From d6e356a5bae73bbb0943a3f6ab001f11d2ce99d7 Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Fri, 30 Aug 2024 23:14:31 -0600 Subject: [PATCH 01/13] inital meraki module and metricsets --- x-pack/metricbeat/include/list.go | 10 + .../metricbeat/module/meraki/_meta/config.yml | 6 + .../module/meraki/_meta/docs.asciidoc | 2 + .../metricbeat/module/meraki/_meta/fields.yml | 10 + .../appliance_uplink_overview/_meta/data.json | 19 ++ .../_meta/docs.asciidoc | 1 + .../_meta/fields.yml | 10 + .../appliance_uplink_overview.go | 102 ++++++++ .../_meta/data.json | 19 ++ .../_meta/docs.asciidoc | 1 + .../_meta/fields.yml | 10 + .../appliance_uplink_status_and_ha.go | 146 +++++++++++ .../_meta/data.json | 19 ++ .../_meta/docs.asciidoc | 1 + .../_meta/fields.yml | 10 + .../cellular_gateway_uplink_status.go | 155 +++++++++++ .../_meta/data.json | 19 ++ .../_meta/docs.asciidoc | 1 + .../_meta/fields.yml | 10 + .../device_appliance_performance_score.go | 198 ++++++++++++++ .../types.go | 15 ++ .../meraki/device_status/_meta/data.json | 19 ++ .../meraki/device_status/_meta/docs.asciidoc | 1 + .../meraki/device_status/_meta/fields.yml | 10 + .../meraki/device_status/device_status.go | 167 ++++++++++++ .../module/meraki/device_status/types.go | 16 ++ .../_meta/data.json | 19 ++ .../_meta/docs.asciidoc | 1 + .../_meta/fields.yml | 10 + .../device_uplink_loss_and_latency.go | 187 +++++++++++++ .../device_uplink_loss_and_latency/types.go | 26 ++ x-pack/metricbeat/module/meraki/doc.go | 2 + .../meraki/license_overview/_meta/data.json | 19 ++ .../license_overview/_meta/docs.asciidoc | 1 + .../meraki/license_overview/_meta/fields.yml | 10 + .../license_overview/license_overview.go | 245 ++++++++++++++++++ .../module/meraki/license_overview/types.go | 37 +++ x-pack/metricbeat/module/meraki/meraki.go | 164 ++++++++++++ .../_meta/data.json | 19 ++ .../_meta/docs.asciidoc | 1 + .../_meta/fields.yml | 10 + .../network_health_channel_utilization.go | 199 ++++++++++++++ x-pack/metricbeat/module/meraki/types.go | 22 ++ .../_meta/data.json | 19 ++ .../_meta/docs.asciidoc | 1 + .../_meta/fields.yml | 10 + .../types.go | 30 +++ .../wireless_device_channel_utilization.go | 149 +++++++++++ .../metricbeat/modules.d/meraki.yml.disabled | 25 ++ 49 files changed, 2183 insertions(+) create mode 100644 x-pack/metricbeat/module/meraki/_meta/config.yml create mode 100644 x-pack/metricbeat/module/meraki/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_overview/appliance_uplink_overview.go create mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/appliance_uplink_status_and_ha.go create mode 100644 x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/cellular_gateway_uplink_status.go create mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/device_appliance_performance_score.go create mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/types.go create mode 100644 x-pack/metricbeat/module/meraki/device_status/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/device_status/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/device_status/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/device_status/device_status.go create mode 100644 x-pack/metricbeat/module/meraki/device_status/types.go create mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/device_uplink_loss_and_latency.go create mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/types.go create mode 100644 x-pack/metricbeat/module/meraki/doc.go create mode 100644 x-pack/metricbeat/module/meraki/license_overview/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/license_overview/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/license_overview/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/license_overview/license_overview.go create mode 100644 x-pack/metricbeat/module/meraki/license_overview/types.go create mode 100644 x-pack/metricbeat/module/meraki/meraki.go create mode 100644 x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/network_health_channel_utilization/network_health_channel_utilization.go create mode 100644 x-pack/metricbeat/module/meraki/types.go create mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/data.json create mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/types.go create mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/wireless_device_channel_utilization.go create mode 100644 x-pack/metricbeat/modules.d/meraki.yml.disabled diff --git a/x-pack/metricbeat/include/list.go b/x-pack/metricbeat/include/list.go index 492e4e7d4d0..2e0b9582b86 100644 --- a/x-pack/metricbeat/include/list.go +++ b/x-pack/metricbeat/include/list.go @@ -48,6 +48,16 @@ import ( _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/mesh" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/mixer" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/pilot" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/appliance_uplink_overview" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_appliance_performance_score" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_status" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/license_overview" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/network_health_channel_utilization" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/performance" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/transaction_log" diff --git a/x-pack/metricbeat/module/meraki/_meta/config.yml b/x-pack/metricbeat/module/meraki/_meta/config.yml new file mode 100644 index 00000000000..f7513df6a64 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/_meta/config.yml @@ -0,0 +1,6 @@ +- module: meraki + metricsets: ["appliance_uplinks_status_and_ha"] + enabled: false + period: 10s + hosts: ["localhost"] + diff --git a/x-pack/metricbeat/module/meraki/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/_meta/docs.asciidoc new file mode 100644 index 00000000000..f2cc90ab62f --- /dev/null +++ b/x-pack/metricbeat/module/meraki/_meta/docs.asciidoc @@ -0,0 +1,2 @@ +This is the meraki module. + diff --git a/x-pack/metricbeat/module/meraki/_meta/fields.yml b/x-pack/metricbeat/module/meraki/_meta/fields.yml new file mode 100644 index 00000000000..cb687419f88 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/_meta/fields.yml @@ -0,0 +1,10 @@ +- key: meraki + title: "meraki" + release: beta + description: > + meraki module + fields: + - name: meraki + type: group + description: > + fields: diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/data.json b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/data.json new file mode 100644 index 00000000000..006350aa627 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"appliance_uplink_overview", + "rtt":44269 + }, + "meraki":{ + "appliance_uplink_overview":{ + "example": "appliance_uplink_overview" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/docs.asciidoc new file mode 100644 index 00000000000..d595e1a372f --- /dev/null +++ b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the appliance_uplink_overview metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/fields.yml b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/fields.yml new file mode 100644 index 00000000000..dac8f9c4a02 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: appliance_uplink_overview + type: group + release: beta + description: > + appliance_uplink_overview + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/appliance_uplink_overview.go b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/appliance_uplink_overview.go new file mode 100644 index 00000000000..d0e6a5f22e9 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/appliance_uplink_overview.go @@ -0,0 +1,102 @@ +package appliance_uplink_overview + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet(meraki.ModuleName, "appliance_uplink_overview", New) +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki appliance_uplink_overview metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + + for _, org := range m.organizations { + + val, res, err := m.client.Appliance.GetOrganizationApplianceUplinksStatusesOverview(org) + + if err != nil { + return fmt.Errorf("Appliance.GetOrganizationApplianceUplinksStatusesOverview failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + reporter.Event(mb.Event{ + MetricSetFields: mapstr.M{ + "id": org, + "active": *val.Counts.ByStatus.Active, + "ready": *val.Counts.ByStatus.Ready, + "connecting": *val.Counts.ByStatus.Connecting, + "failed": *val.Counts.ByStatus.Failed, + "notconnected": *val.Counts.ByStatus.NotConnected, + }, + }) + } + + return nil +} diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/data.json b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/data.json new file mode 100644 index 00000000000..db977761dc2 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"appliance_uplink_status_and_ha", + "rtt":44269 + }, + "meraki":{ + "appliance_uplink_status_and_ha":{ + "example": "appliance_uplink_status_and_ha" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/docs.asciidoc new file mode 100644 index 00000000000..76ee098bda2 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the appliance_uplink_status_and_ha metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/fields.yml b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/fields.yml new file mode 100644 index 00000000000..f5846c50baa --- /dev/null +++ b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: appliance_uplink_status_and_ha + type: group + release: beta + description: > + appliance_uplink_status_and_ha + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/appliance_uplink_status_and_ha.go b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/appliance_uplink_status_and_ha.go new file mode 100644 index 00000000000..32fa49a71e7 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/appliance_uplink_status_and_ha.go @@ -0,0 +1,146 @@ +package appliance_uplink_status_and_ha + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet(meraki.ModuleName, "appliance_uplink_status_and_ha", New) +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki appliance_uplink_status_and_ha metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + for _, org := range m.organizations { + + devices, err := meraki.GetDevices(m.client, org) + if err != nil { + return err + } + + val, res, err := m.client.Appliance.GetOrganizationApplianceUplinkStatuses(org, &meraki_api.GetOrganizationApplianceUplinkStatusesQueryParams{}) + if err != nil { + return fmt.Errorf("Appliance.GetOrganizationApplianceUplinkStatuses failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + reportApplianceUplinkStatuses(reporter, org, devices, val) + } + + return nil +} + +func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, responseApplianceUplinkStatuses *meraki_api.ResponseApplianceGetOrganizationApplianceUplinkStatuses) { + + metrics := []mapstr.M{} + + for _, uplink := range *responseApplianceUplinkStatuses { + + if device, ok := devices[meraki.Serial(uplink.Serial)]; ok { + metric := mapstr.M{ + "appliance.uplink.high_availablity.enabled": uplink.HighAvailability.Enabled, + "appliance.uplink.high_availablity.role": uplink.HighAvailability.Role, + "appliance.uplink.last_reported_at": uplink.LastReportedAt, + "appliance.address": device.Address, + "appliance.firmware": device.Firmware, + "appliance.imei": device.Imei, + "appliance.lan_ip": device.LanIP, + "appliance.location": device.Location, + "appliance.mac": device.Mac, + "appliance.model": device.Model, + "appliance.name": device.Name, + "appliance.network_id": device.NetworkID, + "appliance.notes": device.Notes, + "appliance.product_type": device.ProductType, + "appliance.serial": device.Serial, + "appliance.tags": device.Tags, + } + + //Not sure if this is really needed on uplink status + // for k, v := range device.Details { + // metric[fmt.Sprintf("appliance.details.%s", k)] = v + // } + + for _, item := range *uplink.Uplinks { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "appliance.uplink.interface": item.Interface, + "appliance.uplink.status": item.Status, + "appliance.uplink.ip": item.IP, + "appliance.uplink.gateway": item.Gateway, + "appliance.uplink.public_ip": item.PublicIP, + "appliance.uplink.primary_dns": item.PrimaryDNS, + "appliance.uplink.secondary_dns": item.SecondaryDNS, + "appliance.uplink.ip_assigned_by": item.IPAssignedBy, + })) + + } + } + } + + meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/data.json b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/data.json new file mode 100644 index 00000000000..34914db44e2 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"cellular_gateway_uplink_status", + "rtt":44269 + }, + "meraki":{ + "cellular_gateway_uplink_status":{ + "example": "cellular_gateway_uplink_status" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/docs.asciidoc new file mode 100644 index 00000000000..bda6eb47a51 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the cellular_gateway_uplink_status metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/fields.yml b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/fields.yml new file mode 100644 index 00000000000..4f43ea7ac64 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: cellular_gateway_uplink_status + type: group + release: beta + description: > + cellular_gateway_uplink_status + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/cellular_gateway_uplink_status.go b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/cellular_gateway_uplink_status.go new file mode 100644 index 00000000000..cfa3fd9d2db --- /dev/null +++ b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/cellular_gateway_uplink_status.go @@ -0,0 +1,155 @@ +package cellular_gateway_uplink_status + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet(meraki.ModuleName, "cellular_gateway_uplink_status", New) +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki cellular_gateway_uplink_status metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + for _, org := range m.organizations { + + devices, err := meraki.GetDevices(m.client, org) + if err != nil { + return err + } + + val, res, err := m.client.CellularGateway.GetOrganizationCellularGatewayUplinkStatuses(org, &meraki_api.GetOrganizationCellularGatewayUplinkStatusesQueryParams{}) + if err != nil { + return fmt.Errorf("CellularGateway.GetOrganizationCellularGatewayUplinkStatuses failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + //fmt.Printf("CellularGateway.GetOrganizationCellularGatewayUplinkStatuses debug; [%d] %s.", res.StatusCode(), res.Body()) + + reportApplianceUplinkStatuses(reporter, org, devices, val) + } + + return nil +} + +func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, responseCellularGatewayUplinkStatuses *meraki_api.ResponseCellularGatewayGetOrganizationCellularGatewayUplinkStatuses) { + + metrics := []mapstr.M{} + + for _, uplink := range *responseCellularGatewayUplinkStatuses { + + if device, ok := devices[meraki.Serial(uplink.Serial)]; ok { + metric := mapstr.M{ + //this one should be deleted, I just want to see if it matches the device.network_id + "cellular.gateway.uplink.networkd_id": uplink.NetworkID, + "cellular.gateway.uplink.last_reported_at": uplink.LastReportedAt, + "cellular.gateway.address": device.Address, + "cellular.gateway.firmware": device.Firmware, + "cellular.gateway.imei": device.Imei, + "cellular.gateway.lan_ip": device.LanIP, + "cellular.gateway.location": device.Location, + "cellular.gateway.mac": device.Mac, + "cellular.gateway.model": device.Model, + "cellular.gateway.name": device.Name, + "cellular.gateway.network_id": device.NetworkID, + "cellular.gateway.notes": device.Notes, + "cellular.gateway.product_type": device.ProductType, + "cellular.gateway.serial": device.Serial, + "cellular.gateway.tags": device.Tags, + } + + //Not sure if this is really needed on uplink status + // for k, v := range device.Details { + // metric[fmt.Sprintf("cellular.gateway.details.%s", k)] = v + // } + + // #FIXME - Review might need to be like other uplinks + for i, item := range *uplink.Uplinks { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + fmt.Sprintf("cellular.gateway.uplink.item_%d.apn", i): item.Apn, + fmt.Sprintf("cellular.gateway.uplink.item_%d.connection_type", i): item.ConnectionType, + fmt.Sprintf("cellular.gateway.uplink.item_%d.dns1", i): item.DNS1, + fmt.Sprintf("cellular.gateway.uplink.item_%d.dns2", i): item.DNS2, + fmt.Sprintf("cellular.gateway.uplink.item_%d.gateway", i): item.Gateway, + fmt.Sprintf("cellular.gateway.uplink.item_%d.iccid", i): item.Iccid, + fmt.Sprintf("cellular.gateway.uplink.item_%d.interface", i): item.Interface, + fmt.Sprintf("cellular.gateway.uplink.item_%d.ip", i): item.IP, + fmt.Sprintf("cellular.gateway.uplink.item_%d.model", i): item.Model, + fmt.Sprintf("cellular.gateway.uplink.item_%d.provider", i): item.Provider, + fmt.Sprintf("cellular.gateway.uplink.item_%d.public_ip", i): item.PublicIP, + fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_stat.rsrp", i): item.SignalStat.Rsrp, + fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_stat.rsrq", i): item.SignalStat.Rsrq, + fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_type", i): item.SignalType, + fmt.Sprintf("cellular.gateway.uplink.item_%d.status", i): item.Status, + })) + + } + } + } + meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/data.json b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/data.json new file mode 100644 index 00000000000..959d5682cba --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"device_appliance_performance_score", + "rtt":44269 + }, + "meraki":{ + "device_appliance_performance_score":{ + "example": "device_appliance_performance_score" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/docs.asciidoc new file mode 100644 index 00000000000..c5647ff9765 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the device_appliance_performance_score metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/fields.yml b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/fields.yml new file mode 100644 index 00000000000..c4e88ac8d14 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: device_appliance_performance_score + type: group + release: beta + description: > + device_appliance_performance_score + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/device_appliance_performance_score.go b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/device_appliance_performance_score.go new file mode 100644 index 00000000000..d3c67c94f6a --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/device_appliance_performance_score.go @@ -0,0 +1,198 @@ +package device_appliance_performance_score + +import ( + "encoding/json" + "fmt" + "io" + "log" + "strings" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet(meraki.ModuleName, "device_appliance_performance_score", New) +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string + meraki_url string + meraki_apikey string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki device_appliance_performance_score metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + meraki_url: config.BaseURL, + meraki_apikey: config.ApiKey, + }, nil + +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + for _, org := range m.organizations { + + devices, err := meraki.GetDevices(m.client, org) + if err != nil { + return err + } + + mx_devices := pruneDevicesForMxOnly(devices) + mx_scores, _ := getDevicePerformanceScores(m.meraki_url, m.meraki_apikey, mx_devices) + + reportPerformanceScoreMetrics(reporter, org, mx_devices, mx_scores) + + } + + return nil +} + +func getDevicePerformanceScores(url string, token string, mx_devices map[meraki.Serial]*meraki.Device) (map[meraki.Serial]*DevicePerformanceScore, error) { + + scores := make(map[meraki.Serial]*DevicePerformanceScore) + for _, device := range mx_devices { + + perf_score, status_code := getDevicePerformanceScoresBySerialId(url, token, device.Serial) + + scores[meraki.Serial(device.Serial)] = &DevicePerformanceScore{ + PerformanceScore: perf_score, + HttpStatusCode: status_code, + } + } + + return scores, nil +} + +func pruneDevicesForMxOnly(devices map[meraki.Serial]*meraki.Device) map[meraki.Serial]*meraki.Device { + + mx_devices := make(map[meraki.Serial]*meraki.Device) + for k, v := range devices { + if strings.Index(v.Model, "MX") == 0 { + mx_devices[k] = v + } + } + return mx_devices +} + +func getDevicePerformanceScoresBySerialId(base_url string, token string, serial string) (float64, int) { + //https://developer.cisco.com/meraki/api-v1/get-device-appliance-performance/ + url := base_url + "/api/v1/devices/" + serial + "/appliance/performance" + + // NEED TO ADD RETRY LOGIC + //https://github.com/meraki/dashboard-api-go/blob/25b775d00e5c392677399e4fb1dfb0cfb67badce/sdk/api_client.go#L104C1-L123C3 + //https://developer.cisco.com/meraki/api-v1/rate-limit/#rate-limit + + response, err := meraki.HttpGetRequestWithMerakiRetry(url, token, 5) + + if err != nil { + log.Fatal(err) + } + + responseData, err := io.ReadAll(response.Body) + if err != nil { + log.Fatal(err) + } + + var responseObject PerfScore + json.Unmarshal(responseData, &responseObject) + + //var tmp_float float64 + tmp_float := responseObject.PerformanceScore + + return tmp_float, response.StatusCode + +} + +func reportPerformanceScoreMetrics(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, devicePerformanceScores map[meraki.Serial]*DevicePerformanceScore) { + devicePerformanceScoreMetrics := []mapstr.M{} + for serial, device := range devices { + metric := mapstr.M{ + "device.appliance.address": device.Address, + "device.appliance.firmware": device.Firmware, + "device.appliance.imei": device.Imei, + "device.appliance.lan_ip": device.LanIP, + "device.appliance.location": device.Location, + "device.appliance.mac": device.Mac, + "device.appliance.model": device.Model, + "device.appliance.name": device.Name, + "device.appliance.network_id": device.NetworkID, + "device.appliance.notes": device.Notes, + "device.appliance.product_type": device.ProductType, + "device.appliance.serial": device.Serial, + "device.appliance.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.appliance.details.%s", k)] = v + } + + if score, ok := devicePerformanceScores[serial]; ok { + if score.HttpStatusCode == 204 { + metric["device.appliance.performance.http_status_code"] = score.HttpStatusCode + } else { + metric["device.appliance.performance.score"] = score.PerformanceScore + } + + } + devicePerformanceScoreMetrics = append(devicePerformanceScoreMetrics, metric) + } + + meraki.ReportMetricsForOrganization(reporter, organizationID, devicePerformanceScoreMetrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/types.go b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/types.go new file mode 100644 index 00000000000..5ce78213f07 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/types.go @@ -0,0 +1,15 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_appliance_performance_score + +type PerfScore struct { + PerformanceScore float64 `json:"perfScore"` +} + +// DeviceStatus contains dynamic device attributes +type DevicePerformanceScore struct { + PerformanceScore float64 + HttpStatusCode int +} diff --git a/x-pack/metricbeat/module/meraki/device_status/_meta/data.json b/x-pack/metricbeat/module/meraki/device_status/_meta/data.json new file mode 100644 index 00000000000..3f7d55cd174 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_status/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"device_status", + "rtt":44269 + }, + "meraki":{ + "device_status":{ + "example": "device_status" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/device_status/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/device_status/_meta/docs.asciidoc new file mode 100644 index 00000000000..59cb1786427 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_status/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the device_status metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/device_status/_meta/fields.yml b/x-pack/metricbeat/module/meraki/device_status/_meta/fields.yml new file mode 100644 index 00000000000..e245f90f4f2 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_status/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: device_status + type: group + release: beta + description: > + device_status + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/device_status/device_status.go b/x-pack/metricbeat/module/meraki/device_status/device_status.go new file mode 100644 index 00000000000..ddae555ddc6 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_status/device_status.go @@ -0,0 +1,167 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_status + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + + mb.Registry.MustAddMetricSet(meraki.ModuleName, "device_status", New) + +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki device_status metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + + for _, org := range m.organizations { + //devices, err := getDevices(m.client, org) + devices, err := meraki.GetDevices(m.client, org) + if err != nil { + return err + } + + deviceStatuses, err := getDeviceStatuses(m.client, org) + if err != nil { + return err + } + + reportDeviceStatusMetrics(reporter, org, devices, deviceStatuses) + + } + + return nil +} + +func getDeviceStatuses(client *meraki_api.Client, organizationID string) (map[meraki.Serial]*DeviceStatus, error) { + val, res, err := client.Organizations.GetOrganizationDevicesStatuses(organizationID, &meraki_api.GetOrganizationDevicesStatusesQueryParams{}) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevicesStatuses failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + statuses := make(map[meraki.Serial]*DeviceStatus) + for _, status := range *val { + statuses[meraki.Serial(status.Serial)] = &DeviceStatus{ + Gateway: status.Gateway, + IPType: status.IPType, + LastReportedAt: status.LastReportedAt, + PrimaryDNS: status.PrimaryDNS, + PublicIP: status.PublicIP, + SecondaryDNS: status.SecondaryDNS, + Status: status.Status, + } + } + + return statuses, nil +} + +func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, deviceStatuses map[meraki.Serial]*DeviceStatus) { + deviceStatusMetrics := []mapstr.M{} + for serial, device := range devices { + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v + } + + if status, ok := deviceStatuses[serial]; ok { + metric["device.status.gateway"] = status.Gateway + metric["device.status.ip_type"] = status.IPType + metric["device.status.last_reported_at"] = status.LastReportedAt + metric["device.status.primary_dns"] = status.PrimaryDNS + metric["device.status.public_ip"] = status.PublicIP + metric["device.status.secondary_dns"] = status.SecondaryDNS + metric["device.status.status"] = status.Status + } + deviceStatusMetrics = append(deviceStatusMetrics, metric) + } + + meraki.ReportMetricsForOrganization(reporter, organizationID, deviceStatusMetrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_status/types.go b/x-pack/metricbeat/module/meraki/device_status/types.go new file mode 100644 index 00000000000..600eef17eb9 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_status/types.go @@ -0,0 +1,16 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_status + +// DeviceStatus contains dynamic device attributes +type DeviceStatus struct { + Gateway string + IPType string + LastReportedAt string + PrimaryDNS string + PublicIP string + SecondaryDNS string + Status string // one of ["online", "alerting", "offline", "dormant"] +} diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/data.json b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/data.json new file mode 100644 index 00000000000..41778cb4728 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"device_uplink_loss_and_latency", + "rtt":44269 + }, + "meraki":{ + "device_uplink_loss_and_latency":{ + "example": "device_uplink_loss_and_latency" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/docs.asciidoc new file mode 100644 index 00000000000..47778a7c896 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the device_uplink_loss_and_latency metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/fields.yml b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/fields.yml new file mode 100644 index 00000000000..e994fdc1aee --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: device_uplink_loss_and_latency + type: group + release: beta + description: > + device_uplink_loss_and_latency + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/device_uplink_loss_and_latency.go b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/device_uplink_loss_and_latency.go new file mode 100644 index 00000000000..1c2fd17ec7b --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/device_uplink_loss_and_latency.go @@ -0,0 +1,187 @@ +package device_uplink_loss_and_latency + +import ( + "fmt" + "time" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet(meraki.ModuleName, "device_uplink_loss_and_latency", New) +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki device_uplink_loss_and_latency metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + for _, org := range m.organizations { + + devices, err := meraki.GetDevices(m.client, org) + if err != nil { + return err + } + + uplinks, err := getDeviceUplinkMetrics(m.client, org, m.BaseMetricSet.Module().Config().Period) + if err != nil { + return err + } + + reportDeviceUplinkMetrics(reporter, org, devices, uplinks) + + } + + return nil +} + +func getDeviceUplinkMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { + val, res, err := client.Organizations.GetOrganizationDevicesUplinksLossAndLatency( + organizationID, + &meraki_api.GetOrganizationDevicesUplinksLossAndLatencyQueryParams{ + Timespan: period.Seconds() + 10, // slightly longer than the fetch period to ensure we don't miss measurements due to jitter + }, + ) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevicesUplinksLossAndLatency failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + var uplinks []*Uplink + + for _, device := range *val { + uplink := &Uplink{ + DeviceSerial: meraki.Serial(device.Serial), + IP: device.IP, + Interface: device.Uplink, + } + + for _, measurement := range *device.TimeSeries { + if measurement.LossPercent != nil || measurement.LatencyMs != nil { + timestamp, err := time.Parse(time.RFC3339, measurement.Ts) + if err != nil { + return nil, fmt.Errorf("failed to parse timestamp [%s] in ResponseOrganizationsGetOrganizationDevicesUplinksLossAndLatency: %w", measurement.Ts, err) + } + + metric := UplinkMetric{Timestamp: timestamp} + if measurement.LossPercent != nil { + metric.LossPercent = measurement.LossPercent + } + if measurement.LatencyMs != nil { + metric.LatencyMs = measurement.LatencyMs + } + uplink.Metrics = append(uplink.Metrics, &metric) + } + } + + if len(uplink.Metrics) != 0 { + uplinks = append(uplinks, uplink) + } + } + + return uplinks, nil +} + +func reportDeviceUplinkMetrics(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, uplinks []*Uplink) { + metrics := []mapstr.M{} + + for _, uplink := range uplinks { + if device, ok := devices[uplink.DeviceSerial]; ok { + metric := mapstr.M{ + "uplink.ip": uplink.IP, + "upliink.interface": uplink.Interface, + // fixme: repeated code serializing device metadata to mapstr + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v + } + + for _, uplinkMetric := range uplink.Metrics { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "@timestamp": uplinkMetric.Timestamp, + "uplink.loss_percent": uplinkMetric.LossPercent, + "uplink.latency_ms": uplinkMetric.LatencyMs, + })) + } + } else { + // missing device metadata; ignore + } + } + meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/types.go b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/types.go new file mode 100644 index 00000000000..364b001ce79 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/types.go @@ -0,0 +1,26 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_uplink_loss_and_latency + +import ( + "time" + + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" +) + +// Uplink contains static device uplink attributes; uplinks are always associated with a device +type Uplink struct { + DeviceSerial meraki.Serial + IP string + Interface string + Metrics []*UplinkMetric +} + +// UplinkMetric contains timestamped device uplink metric data points +type UplinkMetric struct { + Timestamp time.Time + LossPercent *float64 + LatencyMs *float64 +} diff --git a/x-pack/metricbeat/module/meraki/doc.go b/x-pack/metricbeat/module/meraki/doc.go new file mode 100644 index 00000000000..3a33bfa283c --- /dev/null +++ b/x-pack/metricbeat/module/meraki/doc.go @@ -0,0 +1,2 @@ +// Package meraki is a Metricbeat module that contains MetricSets. +package meraki diff --git a/x-pack/metricbeat/module/meraki/license_overview/_meta/data.json b/x-pack/metricbeat/module/meraki/license_overview/_meta/data.json new file mode 100644 index 00000000000..f9045a48a76 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/license_overview/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"license_overview", + "rtt":44269 + }, + "meraki":{ + "license_overview":{ + "example": "license_overview" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/license_overview/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/license_overview/_meta/docs.asciidoc new file mode 100644 index 00000000000..e5f93e98a90 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/license_overview/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the license_overview metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/license_overview/_meta/fields.yml b/x-pack/metricbeat/module/meraki/license_overview/_meta/fields.yml new file mode 100644 index 00000000000..bfc580b5086 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/license_overview/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: license_overview + type: group + release: beta + description: > + license_overview + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/license_overview/license_overview.go b/x-pack/metricbeat/module/meraki/license_overview/license_overview.go new file mode 100644 index 00000000000..52a3328b916 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/license_overview/license_overview.go @@ -0,0 +1,245 @@ +package license_overview + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet(meraki.ModuleName, "license_overview", New) +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki license_overview metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + for _, org := range m.organizations { + + cotermLicenses, perDeviceLicenses, systemsManagerLicense, err := getLicenseStates(m.client, org) + if err != nil { + return err + } + + reportLicenseMetrics(reporter, org, cotermLicenses, perDeviceLicenses, systemsManagerLicense) + } + + return nil +} + +func getLicenseStates(client *meraki_api.Client, organizationID string) ([]*CoterminationLicense, []*PerDeviceLicense, *SystemsManagerLicense, error) { + val, res, err := client.Organizations.GetOrganizationLicensesOverview(organizationID) + + if err != nil { + return nil, nil, nil, fmt.Errorf("GetOrganizationLicensesOverview failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + var cotermLicenses []*CoterminationLicense + var perDeviceLicenses []*PerDeviceLicense + var systemsManagerLicense *SystemsManagerLicense + + // co-termination license metrics (all devices share a single expiration date and status) are reported as counts of licenses per-device + if val.LicensedDeviceCounts != nil { + // i don't know why this isn't typed in the SDK - slightly worrying + for device, count := range (*val.LicensedDeviceCounts).(map[string]interface{}) { + cotermLicenses = append(cotermLicenses, &CoterminationLicense{ + DeviceModel: device, + Count: count, + ExpirationDate: val.ExpirationDate, + Status: val.Status, + }) + } + } + + // per-device license metrics (each device has its own expiration date and status) are reported counts of licenses per-state + if val.States != nil { + if val.States.Active != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Active", + Count: val.States.Active.Count, + }) + } + + if val.States.Expired != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Expired", + Count: val.States.Expired.Count, + }) + } + + if val.States.RecentlyQueued != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "RecentlyQueued", + Count: val.States.RecentlyQueued.Count, + }) + } + + if val.States.Expiring != nil { + if val.States.Expiring.Critical != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Expiring", + ExpirationState: "Critical", + Count: val.States.Expiring.Critical.ExpiringCount, + ExpirationThresholdDays: val.States.Expiring.Critical.ThresholdInDays, + }) + } + + if val.States.Expiring.Warning != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Expiring", + ExpirationState: "Warning", + Count: val.States.Expiring.Warning.ExpiringCount, + ExpirationThresholdDays: val.States.Expiring.Warning.ThresholdInDays, + }) + } + } + + if val.States.Unused != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Unused", + Count: val.States.Unused.Count, + SoonestActivationDate: val.States.Unused.SoonestActivation.ActivationDate, + SoonestActivationCount: val.States.Unused.SoonestActivation.ToActivateCount, + }) + } + + if val.States.UnusedActive != nil { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "UnusedActive", + Count: val.States.UnusedActive.Count, + OldestActivationDate: val.States.UnusedActive.OldestActivation.ActivationDate, + OldestActivationCount: val.States.UnusedActive.OldestActivation.ActiveCount, + }) + } + } + + if val.LicenseTypes != nil { + for _, t := range *val.LicenseTypes { + perDeviceLicenses = append(perDeviceLicenses, &PerDeviceLicense{ + State: "Unassigned", + Type: t.LicenseType, + Count: t.Counts.Unassigned, + }) + } + } + + // per-device metrics also contain systems manager metrics + if val.SystemsManager != nil { + systemsManagerLicense = &SystemsManagerLicense{ + TotalSeats: val.SystemsManager.Counts.TotalSeats, + ActiveSeats: val.SystemsManager.Counts.ActiveSeats, + UnassignedSeats: val.SystemsManager.Counts.UnassignedSeats, + OrgwideEnrolledDevices: val.SystemsManager.Counts.OrgwideEnrolledDevices, + } + } + + return cotermLicenses, perDeviceLicenses, systemsManagerLicense, nil +} + +func reportLicenseMetrics(reporter mb.ReporterV2, organizationID string, cotermLicenses []*CoterminationLicense, perDeviceLicenses []*PerDeviceLicense, systemsManagerLicense *SystemsManagerLicense) { + if len(cotermLicenses) != 0 { + cotermLicenseMetrics := []mapstr.M{} + for _, license := range cotermLicenses { + cotermLicenseMetrics = append(cotermLicenseMetrics, mapstr.M{ + "license.device_model": license.DeviceModel, + "license.expiration_date": license.ExpirationDate, + "license.status": license.Status, + "license.count": license.Count, + }) + } + meraki.ReportMetricsForOrganization(reporter, organizationID, cotermLicenseMetrics) + } + + if len(perDeviceLicenses) != 0 { + perDeviceLicenseMetrics := []mapstr.M{} + for _, license := range perDeviceLicenses { + perDeviceLicenseMetrics = append(perDeviceLicenseMetrics, mapstr.M{ + "license.state": license.State, + "license.count": license.Count, + "license.expiration_state": license.ExpirationState, + "license.expiration_threshold_days": license.ExpirationThresholdDays, + "license.soonest_activation_date": license.SoonestActivationDate, + "license.soonest_activation_count": license.SoonestActivationCount, + "license.oldest_activation_date": license.OldestActivationDate, + "license.oldest_activation_count": license.OldestActivationCount, + "license.type": license.Type, + }) + } + meraki.ReportMetricsForOrganization(reporter, organizationID, perDeviceLicenseMetrics) + } + + if systemsManagerLicense != nil { + + meraki.ReportMetricsForOrganization(reporter, organizationID, []mapstr.M{ + { + "license.systems_manager.active_seats": systemsManagerLicense.ActiveSeats, + "license.systems_manager.orgwideenrolled_devices": systemsManagerLicense.OrgwideEnrolledDevices, + "license.systems_manager.total_seats": systemsManagerLicense.TotalSeats, + "license.systems_manager.unassigned_seats": systemsManagerLicense.UnassignedSeats, + }, + }) + } +} diff --git a/x-pack/metricbeat/module/meraki/license_overview/types.go b/x-pack/metricbeat/module/meraki/license_overview/types.go new file mode 100644 index 00000000000..5f3d9e102ed --- /dev/null +++ b/x-pack/metricbeat/module/meraki/license_overview/types.go @@ -0,0 +1,37 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package license_overview + +// device unique identifier +type Serial string + +// CoterminationLicense have a common expiration date and are reported per device model +type CoterminationLicense struct { + ExpirationDate string + DeviceModel string + Status string + Count interface{} +} + +// PerDeviceLicense are reported by license state with details on expiration and activations +type PerDeviceLicense struct { + State string // one of ["Active", "Expired", "RecentlyQueued", "Expiring", "Unused", "UnusedActive", "Unassigned"] + Count *int + ExpirationState string // one of ["critial", "warning"] (only for Expiring licenses) + ExpirationThresholdDays *int // only for Expiring licenses + SoonestActivationDate string // only for Unused licenses + SoonestActivationCount *int // only for Unused licenses + OldestActivationDate string // only for UnusedActive licenses + OldestActivationCount *int // only for UnusedActive licenses + Type string // one of ["ENT", "UPGR", "ADV"] (only for Unassigned licenses) +} + +// SystemManagerLicense reports counts for seats and devices +type SystemsManagerLicense struct { + ActiveSeats *int + OrgwideEnrolledDevices *int + TotalSeats *int + UnassignedSeats *int +} diff --git a/x-pack/metricbeat/module/meraki/meraki.go b/x-pack/metricbeat/module/meraki/meraki.go new file mode 100644 index 00000000000..feea666f6d8 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/meraki.go @@ -0,0 +1,164 @@ +package meraki + +import ( + "fmt" + "net/http" + "strconv" + "time" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// ModuleName is the name of this module. +const ModuleName = "meraki" + +// func init() { + +// if err := mb.Registry.AddModule(ModuleName, newModule); err != nil { +// panic(err) +// } +// } + +// // Config defines all required and optional parameters for meraki metricsets +// type Config struct { +// Token string `config:"apiKey" validate:"nonzero,required"` +// Organizations []string `config:"organizations" validate:"nonzero,required"` +// } + +// // newModule adds validation that hosts is non-empty, a requirement to use the +// // mssql module. +// func newModule(base mb.BaseModule) (mb.Module, error) { +// // Validate that at least one host has been specified. +// var config Config +// if err := base.UnpackConfig(&config); err != nil { +// return nil, err +// } + +// return &base, nil +// } + +func ReportMetricsForOrganization(reporter mb.ReporterV2, organizationID string, metrics ...[]mapstr.M) { + + for _, metricSlice := range metrics { + for _, metric := range metricSlice { + event := mb.Event{ModuleFields: mapstr.M{"organization_id": organizationID}} + if ts, ok := metric["@timestamp"].(time.Time); ok { + event.Timestamp = ts + delete(metric, "@timestamp") + } + event.ModuleFields.Update(metric) + reporter.Event(event) + } + } +} + +func GetDevicesByProductType(client *meraki_api.Client, organizationID string, productTypes []string) (map[Serial]*Device, error) { + val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{ + ProductTypes: productTypes, + }) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevices failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + devices := make(map[Serial]*Device) + for _, d := range *val { + device := Device{ + Firmware: d.Firmware, + Imei: d.Imei, + LanIP: d.LanIP, + Location: []*float64{d.Lng, d.Lat}, // (lon, lat) order is important! + Mac: d.Mac, + Model: d.Model, + Name: d.Name, + NetworkID: d.NetworkID, + Notes: d.Notes, + ProductType: d.ProductType, + Serial: d.Serial, + Tags: d.Tags, + } + if d.Details != nil { + for _, detail := range *d.Details { + device.Details[detail.Name] = detail.Value + } + } + devices[Serial(device.Serial)] = &device + } + + return devices, nil +} + +func GetDevices(client *meraki_api.Client, organizationID string) (map[Serial]*Device, error) { + val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{}) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevices failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + devices := make(map[Serial]*Device) + for _, d := range *val { + device := Device{ + Firmware: d.Firmware, + Imei: d.Imei, + LanIP: d.LanIP, + Location: []*float64{d.Lng, d.Lat}, // (lon, lat) order is important! + Mac: d.Mac, + Model: d.Model, + Name: d.Name, + NetworkID: d.NetworkID, + Notes: d.Notes, + ProductType: d.ProductType, + Serial: d.Serial, + Tags: d.Tags, + } + if d.Details != nil { + for _, detail := range *d.Details { + device.Details[detail.Name] = detail.Value + } + } + devices[Serial(device.Serial)] = &device + } + + return devices, nil +} + +func HttpGetRequestWithMerakiRetry(url string, token string, retry int) (*http.Response, error) { + //https://developer.cisco.com/meraki/api-v1/get-device-appliance-performance/ + //url = m.meraki_url + "/api/v1/devices/" + serial + "/appliance/performance" + + // Create a Bearer string by appending string access token + var bearer = "Bearer " + token + + // Create a new request using http + req, _ := http.NewRequest("GET", url, nil) + + // add authorization header to the req + req.Header.Add("Authorization", bearer) + req.Header.Add("Accept", "application/json") + + client := &http.Client{} + response, err := client.Do(req) + + // NEED TO ADD RETRY LOGIC + //https://github.com/meraki/dashboard-api-go/blob/25b775d00e5c392677399e4fb1dfb0cfb67badce/sdk/api_client.go#L104C1-L123C3 + //https://developer.cisco.com/meraki/api-v1/rate-limit/#rate-limit + + for i := 0; i < retry && response.StatusCode == 429; i++ { + + retryHeader := response.Header.Get("Retry-After") + if _, err := strconv.Atoi(retryHeader); err == nil { + time.ParseDuration(retryHeader + "s") + // Debug + // Should we log this? + fmt.Println("Paused for time:" + retryHeader + "s") + } + response, err = client.Do(req) + + } + + return response, err + +} diff --git a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/data.json b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/data.json new file mode 100644 index 00000000000..fcc2b9f0823 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"network_health_channel_utilization", + "rtt":44269 + }, + "meraki":{ + "network_health_channel_utilization":{ + "example": "network_health_channel_utilization" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/docs.asciidoc new file mode 100644 index 00000000000..250fccbabb6 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the network_health_channel_utilization metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/fields.yml b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/fields.yml new file mode 100644 index 00000000000..17996f130fa --- /dev/null +++ b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: network_health_channel_utilization + type: group + release: beta + description: > + network_health_channel_utilization + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/network_health_channel_utilization.go new file mode 100644 index 00000000000..dc4a2c3a5b1 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/network_health_channel_utilization.go @@ -0,0 +1,199 @@ +package network_health_channel_utilization + +import ( + "fmt" + "log" + "strings" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet(meraki.ModuleName, "network_health_channel_utilization", New) +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki network_health_channel_utilization metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + for _, org := range m.organizations { + + //productTypes := []string{"wireless"} + //devices, err := meraki.GetDevicesByProductType(m.client, org, productTypes) + devices, err := meraki.GetDevices(m.client, org) + if err != nil { + return err + } + + orgNetworks, res, err := m.client.Organizations.GetOrganizationNetworks(org, &meraki_api.GetOrganizationNetworksQueryParams{}) + if err != nil { + log.Printf("Organizations.GetOrganizationNetworks failed; [%d] %s.", res.StatusCode(), res.Body()) + return fmt.Errorf("Organizations.GetOrganizationNetworks failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + //fmt.Printf("\n#DEBUG Organizations.GetOrganizationNetworks; [%d] %s.\n###################", res.StatusCode(), res.Body()) + + networkHealthUtilizations, err := getNetworkHealthChannelUtilization(m.client, orgNetworks) + if err != nil { + return err + } + + reportNetworkHealthChannelUtilization(reporter, org, devices, networkHealthUtilizations) + + } + + return nil +} + +func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) ([]*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization, error) { + + var networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization + + for _, network := range *networks { + + for _, product_type := range network.ProductTypes { + + if strings.Compare(product_type, "wireless") == 0 { + + networkHealthUtilization, _, err := client.Networks.GetNetworkNetworkHealthChannelUtilization(network.ID, &meraki_api.GetNetworkNetworkHealthChannelUtilizationQueryParams{}) + if err != nil { + //return nil, fmt.Errorf("Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + //log.Printf("\n#ERROR Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s.", res.StatusCode(), res.Body()) + //fmt.Printf("\n#ERROR Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s.\n###################", res.StatusCode(), res.Body()) + } else { + //fmt.Printf("\n#DEBUG Networks.GetNetworkNetworkHealthChannelUtilization; [%d] %s. \n###################", res.StatusCode(), res.Body()) + networkHealthUtilizations = append(networkHealthUtilizations, networkHealthUtilization) + } + + } + } + } + + return networkHealthUtilizations, nil +} + +func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization) { + + metrics := []mapstr.M{} + //Note: API does not specifiy if wireless devices only, so iterating through all network devices. API is to ambiguous + //for _, device := range devices { + + //fmt.Printf("\n#DEBUG device Info: \nSerial: %s \nName: %s \nMode: %s \nNetworkID: %s \nProducttype: %s", device.Serial, device.Name, device.Model, device.NetworkID, device.ProductType) + + //fmt.Printf("\n#DEBUG reportNetworkHealthCHannelUtilization") + + for _, networkHealthUtil := range networkHealthUtilizations { + //fmt.Printf("\n#DEBUG reportNetworkHealthCHannelUtilization For LOOP 1") + + for _, network := range *networkHealthUtil { + + //fmt.Printf("\n#DEBUG reportNetworkHealthCHannelUtilization For LOOP 2") + + metric := mapstr.M{ + "network.health.channel.radio.serial": network.Serial, + "network.health.channel.radio.model": network.Model, + "network.health.channel.radio.tags": network.Tags, + } + + for _, wifi0 := range *network.Wifi0 { + metric["network.health.channel.radio.wifi0.start_time"] = wifi0.StartTime + metric["network.health.channel.radio.wifi0.end_time"] = wifi0.EndTime + metric["network.health.channel.radio.wifi0.utilization80211"] = wifi0.Utilization80211 + metric["network.health.channel.radio.wifi0.utilizationNon80211"] = wifi0.UtilizationNon80211 + metric["network.health.channel.radio.wifi0.utilizationTotal"] = wifi0.UtilizationTotal + } + + for _, wifi1 := range *network.Wifi1 { + metric["network.health.channel.radio.wifi1.start_time"] = wifi1.StartTime + metric["network.health.channel.radio.wifi1.end_time"] = wifi1.EndTime + metric["network.health.channel.radio.wifi1.utilization80211"] = wifi1.Utilization80211 + metric["network.health.channel.radio.wifi1.utilizationNon80211"] = wifi1.UtilizationNon80211 + metric["network.health.channel.radio.wifi1.utilizationTotal"] = wifi1.UtilizationTotal + } + + if device, ok := devices[meraki.Serial(network.Serial)]; ok { + metric["device.address"] = device.Address + metric["device.firmware"] = device.Firmware + metric["device.imei"] = device.Imei + metric["device.lan_ip"] = device.LanIP + metric["device.location"] = device.Location + metric["device.mac"] = device.Mac + metric["device.model"] = device.Model + metric["device.name"] = device.Name + metric["device.network_id"] = device.NetworkID + metric["device.notes"] = device.Notes + metric["device.product_type"] = device.ProductType + metric["device.serial"] = device.Serial + metric["device.tags"] = device.Tags + + } + metrics = append(metrics, metric) + } + // for k, v := range device.Details { + // metric[fmt.Sprintf("device.details.%s", k)] = v + // } + } + meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/types.go b/x-pack/metricbeat/module/meraki/types.go new file mode 100644 index 00000000000..9b55ea2d86c --- /dev/null +++ b/x-pack/metricbeat/module/meraki/types.go @@ -0,0 +1,22 @@ +package meraki + +// device unique identifier +type Serial string + +// Device contains static device attributes (i.e. dimensions) +type Device struct { + Address string + Details map[string]string + Firmware string + Imei *float64 + LanIP string + Location []*float64 + Mac string + Model string + Name string + NetworkID string + Notes string + ProductType string // one of ["appliance", "camera", "cellularGateway", "secureConnect", "sensor", "switch", "systemsManager", "wireless", "wirelessController"] + Serial string + Tags []string +} diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/data.json b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/data.json new file mode 100644 index 00000000000..d50fc308510 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"meraki", + "name":"wireless_device_channel_utilization", + "rtt":44269 + }, + "meraki":{ + "wireless_device_channel_utilization":{ + "example": "wireless_device_channel_utilization" + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/docs.asciidoc new file mode 100644 index 00000000000..390769cfd3f --- /dev/null +++ b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the wireless_device_channel_utilization metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/fields.yml b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/fields.yml new file mode 100644 index 00000000000..d68a1e7b036 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/fields.yml @@ -0,0 +1,10 @@ +- name: wireless_device_channel_utilization + type: group + release: beta + description: > + wireless_device_channel_utilization + fields: + - name: example + type: keyword + description: > + Example field diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/types.go b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/types.go new file mode 100644 index 00000000000..c7bbbf8723f --- /dev/null +++ b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/types.go @@ -0,0 +1,30 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package wireless_device_channel_utilization + +import ( + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" +) + +// Uplink contains static device uplink attributes; uplinks are always associated with a device +type WirelessDevicesChannelUtilizationByDevice []struct { + Serial meraki.Serial `json:"serial"` + Mac string `json:"mac"` + Network struct { + ID string `json:"id"` + } `json:"network"` + ByBand []struct { + Band string `json:"band"` + Wifi struct { + Percentage float64 `json:"percentage"` + } `json:"wifi"` + NonWifi struct { + Percentage float64 `json:"percentage"` + } `json:"nonWifi"` + Total struct { + Percentage float64 `json:"percentage"` + } `json:"total"` + } `json:"byBand"` +} diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/wireless_device_channel_utilization.go b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/wireless_device_channel_utilization.go new file mode 100644 index 00000000000..a7e9c123dd7 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/wireless_device_channel_utilization.go @@ -0,0 +1,149 @@ +package wireless_device_channel_utilization + +import ( + "encoding/json" + "fmt" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet(meraki.ModuleName, "wireless_device_channel_utilization", New) +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki wireless_device_channel_utilization metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + for _, org := range m.organizations { + + devices, err := meraki.GetDevices(m.client, org) + if err != nil { + return err + } + + res, err := m.client.Devices.GetOrganizationWirelessDevicesChannelUtilizationByDevice(org, &meraki_api.GetOrganizationWirelessDevicesChannelUtilizationByDeviceQueryParams{}) + if err != nil { + return fmt.Errorf("GetOrganizationWirelessDevicesChannelUtilizationByDevice failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + //debug + //fmt.Printf("\n #DEBUG Devices.GetOrganizationWirelessDevicesChannelUtilizationByDevice; [%d] %s.", res.StatusCode(), res.Body()) + + var wirelessDevices WirelessDevicesChannelUtilizationByDevice + err = json.Unmarshal(res.Body(), &wirelessDevices) + if err != nil { + return fmt.Errorf("device_network_health_channel_utilization json umarshal failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + reportWirelessDeviceChannelUtilization(reporter, org, devices, wirelessDevices) + + } + + return nil +} + +func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, wirelessDevices WirelessDevicesChannelUtilizationByDevice) { + + metrics := []mapstr.M{} + + for _, wirelessDevice := range wirelessDevices { + + if device, ok := devices[meraki.Serial(wirelessDevice.Serial)]; ok { + + metric := mapstr.M{ + "wireless.device.address": device.Address, + "wireless.device.firmware": device.Firmware, + "wireless.device.imei": device.Imei, + "wireless.device.lan_ip": device.LanIP, + "wireless.device.location": device.Location, + "wireless.device.mac": device.Mac, + "wireless.device.model": device.Model, + "wireless.device.name": device.Name, + "wireless.device.network_id": device.NetworkID, + "wireless.device.notes": device.Notes, + "wireless.device.product_type": device.ProductType, + "wireless.device.serial": device.Serial, + "wireless.device.tags": device.Tags, + } + + for _, v := range wirelessDevice.ByBand { + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.wifi.percentage", v.Band)] = v.Wifi.Percentage + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.nonwifi.percentage", v.Band)] = v.NonWifi.Percentage + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.total.percentage", v.Band)] = v.Total.Percentage + } + + // for k, v := range device.Details { + // metric[fmt.Sprintf("wireless.device.details.%s", k)] = v + // } + + metrics = append(metrics, metric) + + } + + } + meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/modules.d/meraki.yml.disabled b/x-pack/metricbeat/modules.d/meraki.yml.disabled new file mode 100644 index 00000000000..a7cdac9e3a3 --- /dev/null +++ b/x-pack/metricbeat/modules.d/meraki.yml.disabled @@ -0,0 +1,25 @@ +# Module: meraki +# Docs: https://www.elastic.co/guide/en/beats/metricbeat/main/metricbeat-module-meraki.html + +- module: meraki + metricsets: [ + "device_status", + "appliance_uplink_overview", + "appliance_uplink_status_and_ha", + "cellular_gateway_uplink_status", + "device_appliance_performance_score", + "device_uplink_loss_and_latency", + "license_overview", + "network_health_channel_utilization", + "wireless_device_channel_utilization" + ] + enabled: true + period: 60s + hosts: ["https://api.meraki.com"] + # This can be used for token based authorization: + bearer_token_file: /var/run/secrets/meraki/token + headers: + Accept: application/json + apiKey: + organizations: ["orgId"] + From 9fe235e3948118dba950bbac9b739f9e3ab93915 Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Fri, 6 Sep 2024 20:12:18 -0600 Subject: [PATCH 02/13] initial refactor for single meraki metricset device_health --- x-pack/metricbeat/include/list.go | 13 +- .../appliance_uplink_overview/_meta/data.json | 19 -- .../_meta/docs.asciidoc | 1 - .../_meta/fields.yml | 10 - .../appliance_uplink_overview.go | 102 -------- .../_meta/data.json | 19 -- .../_meta/docs.asciidoc | 1 - .../_meta/fields.yml | 10 - .../appliance_uplink_status_and_ha.go | 146 ------------ .../_meta/data.json | 19 -- .../_meta/docs.asciidoc | 1 - .../_meta/fields.yml | 10 - .../cellular_gateway_uplink_status.go | 155 ------------ .../_meta/data.json | 19 -- .../_meta/docs.asciidoc | 1 - .../_meta/fields.yml | 10 - .../device_appliance_performance_score.go | 198 ---------------- .../types.go | 15 -- .../_meta/data.json | 6 +- .../meraki/device_health/_meta/docs.asciidoc | 1 + .../_meta/fields.yml | 4 +- .../appliance_uplink_status_and_ha.go | 53 +++++ .../cellular_gateway_uplink_status.go | 60 +++++ .../device_appliance_performance_score.go | 114 +++++++++ .../meraki/device_health/device_health.go | 222 ++++++++++++++++++ .../meraki/device_health/device_info.go | 81 +++++++ .../meraki/device_health/device_status.go | 75 ++++++ .../device_uplink_loss_and_latency.go | 104 ++++++++ .../license_overview.go | 89 +------ .../network_health_channel_utilization.go | 88 +++++++ .../module/meraki/device_health/types.go | 116 +++++++++ .../wireless_device_channel_utilization.go | 46 ++++ .../meraki/device_status/_meta/docs.asciidoc | 1 - .../meraki/device_status/device_status.go | 167 ------------- .../module/meraki/device_status/types.go | 16 -- .../_meta/data.json | 19 -- .../_meta/docs.asciidoc | 1 - .../_meta/fields.yml | 10 - .../device_uplink_loss_and_latency.go | 187 --------------- .../device_uplink_loss_and_latency/types.go | 26 -- .../meraki/license_overview/_meta/data.json | 19 -- .../license_overview/_meta/docs.asciidoc | 1 - .../meraki/license_overview/_meta/fields.yml | 10 - .../module/meraki/license_overview/types.go | 37 --- x-pack/metricbeat/module/meraki/meraki.go | 164 ------------- .../_meta/data.json | 19 -- .../_meta/docs.asciidoc | 1 - .../_meta/fields.yml | 10 - .../network_health_channel_utilization.go | 199 ---------------- x-pack/metricbeat/module/meraki/types.go | 22 -- .../_meta/data.json | 19 -- .../_meta/docs.asciidoc | 1 - .../_meta/fields.yml | 10 - .../types.go | 30 --- .../wireless_device_channel_utilization.go | 149 ------------ .../metricbeat/modules.d/meraki.yml.disabled | 12 +- 56 files changed, 972 insertions(+), 1966 deletions(-) delete mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/data.json delete mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/fields.yml delete mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_overview/appliance_uplink_overview.go delete mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/data.json delete mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/fields.yml delete mode 100644 x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/appliance_uplink_status_and_ha.go delete mode 100644 x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/data.json delete mode 100644 x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/fields.yml delete mode 100644 x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/cellular_gateway_uplink_status.go delete mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/data.json delete mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/fields.yml delete mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/device_appliance_performance_score.go delete mode 100644 x-pack/metricbeat/module/meraki/device_appliance_performance_score/types.go rename x-pack/metricbeat/module/meraki/{device_status => device_health}/_meta/data.json (74%) create mode 100644 x-pack/metricbeat/module/meraki/device_health/_meta/docs.asciidoc rename x-pack/metricbeat/module/meraki/{device_status => device_health}/_meta/fields.yml (77%) create mode 100644 x-pack/metricbeat/module/meraki/device_health/appliance_uplink_status_and_ha.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/cellular_gateway_uplink_status.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_health.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_info.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_status.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go rename x-pack/metricbeat/module/meraki/{license_overview => device_health}/license_overview.go (66%) create mode 100644 x-pack/metricbeat/module/meraki/device_health/network_health_channel_utilization.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/types.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/wireless_device_channel_utilization.go delete mode 100644 x-pack/metricbeat/module/meraki/device_status/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/device_status/device_status.go delete mode 100644 x-pack/metricbeat/module/meraki/device_status/types.go delete mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/data.json delete mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/fields.yml delete mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/device_uplink_loss_and_latency.go delete mode 100644 x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/types.go delete mode 100644 x-pack/metricbeat/module/meraki/license_overview/_meta/data.json delete mode 100644 x-pack/metricbeat/module/meraki/license_overview/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/license_overview/_meta/fields.yml delete mode 100644 x-pack/metricbeat/module/meraki/license_overview/types.go delete mode 100644 x-pack/metricbeat/module/meraki/meraki.go delete mode 100644 x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/data.json delete mode 100644 x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/fields.yml delete mode 100644 x-pack/metricbeat/module/meraki/network_health_channel_utilization/network_health_channel_utilization.go delete mode 100644 x-pack/metricbeat/module/meraki/types.go delete mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/data.json delete mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/docs.asciidoc delete mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/fields.yml delete mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/types.go delete mode 100644 x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/wireless_device_channel_utilization.go diff --git a/x-pack/metricbeat/include/list.go b/x-pack/metricbeat/include/list.go index 2e0b9582b86..b4b60ae1d1e 100644 --- a/x-pack/metricbeat/include/list.go +++ b/x-pack/metricbeat/include/list.go @@ -47,17 +47,8 @@ import ( _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/galley" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/mesh" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/mixer" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/pilot" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/appliance_uplink_overview" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_appliance_performance_score" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_status" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/license_overview" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/network_health_channel_utilization" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/pilot" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_health" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/performance" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/transaction_log" diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/data.json b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/data.json deleted file mode 100644 index 006350aa627..00000000000 --- a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" - }, - "metricset":{ - "host":"localhost", - "module":"meraki", - "name":"appliance_uplink_overview", - "rtt":44269 - }, - "meraki":{ - "appliance_uplink_overview":{ - "example": "appliance_uplink_overview" - } - }, - "type":"metricsets" -} diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/docs.asciidoc deleted file mode 100644 index d595e1a372f..00000000000 --- a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the appliance_uplink_overview metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/fields.yml b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/fields.yml deleted file mode 100644 index dac8f9c4a02..00000000000 --- a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- name: appliance_uplink_overview - type: group - release: beta - description: > - appliance_uplink_overview - fields: - - name: example - type: keyword - description: > - Example field diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/appliance_uplink_overview.go b/x-pack/metricbeat/module/meraki/appliance_uplink_overview/appliance_uplink_overview.go deleted file mode 100644 index d0e6a5f22e9..00000000000 --- a/x-pack/metricbeat/module/meraki/appliance_uplink_overview/appliance_uplink_overview.go +++ /dev/null @@ -1,102 +0,0 @@ -package appliance_uplink_overview - -import ( - "fmt" - - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - "github.com/elastic/elastic-agent-libs/logp" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - mb.Registry.MustAddMetricSet(meraki.ModuleName, "appliance_uplink_overview", New) -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki appliance_uplink_overview metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - }, nil -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - - for _, org := range m.organizations { - - val, res, err := m.client.Appliance.GetOrganizationApplianceUplinksStatusesOverview(org) - - if err != nil { - return fmt.Errorf("Appliance.GetOrganizationApplianceUplinksStatusesOverview failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - reporter.Event(mb.Event{ - MetricSetFields: mapstr.M{ - "id": org, - "active": *val.Counts.ByStatus.Active, - "ready": *val.Counts.ByStatus.Ready, - "connecting": *val.Counts.ByStatus.Connecting, - "failed": *val.Counts.ByStatus.Failed, - "notconnected": *val.Counts.ByStatus.NotConnected, - }, - }) - } - - return nil -} diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/data.json b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/data.json deleted file mode 100644 index db977761dc2..00000000000 --- a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" - }, - "metricset":{ - "host":"localhost", - "module":"meraki", - "name":"appliance_uplink_status_and_ha", - "rtt":44269 - }, - "meraki":{ - "appliance_uplink_status_and_ha":{ - "example": "appliance_uplink_status_and_ha" - } - }, - "type":"metricsets" -} diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/docs.asciidoc deleted file mode 100644 index 76ee098bda2..00000000000 --- a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the appliance_uplink_status_and_ha metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/fields.yml b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/fields.yml deleted file mode 100644 index f5846c50baa..00000000000 --- a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- name: appliance_uplink_status_and_ha - type: group - release: beta - description: > - appliance_uplink_status_and_ha - fields: - - name: example - type: keyword - description: > - Example field diff --git a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/appliance_uplink_status_and_ha.go b/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/appliance_uplink_status_and_ha.go deleted file mode 100644 index 32fa49a71e7..00000000000 --- a/x-pack/metricbeat/module/meraki/appliance_uplink_status_and_ha/appliance_uplink_status_and_ha.go +++ /dev/null @@ -1,146 +0,0 @@ -package appliance_uplink_status_and_ha - -import ( - "fmt" - - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - "github.com/elastic/elastic-agent-libs/logp" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - mb.Registry.MustAddMetricSet(meraki.ModuleName, "appliance_uplink_status_and_ha", New) -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki appliance_uplink_status_and_ha metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - }, nil -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - for _, org := range m.organizations { - - devices, err := meraki.GetDevices(m.client, org) - if err != nil { - return err - } - - val, res, err := m.client.Appliance.GetOrganizationApplianceUplinkStatuses(org, &meraki_api.GetOrganizationApplianceUplinkStatusesQueryParams{}) - if err != nil { - return fmt.Errorf("Appliance.GetOrganizationApplianceUplinkStatuses failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - reportApplianceUplinkStatuses(reporter, org, devices, val) - } - - return nil -} - -func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, responseApplianceUplinkStatuses *meraki_api.ResponseApplianceGetOrganizationApplianceUplinkStatuses) { - - metrics := []mapstr.M{} - - for _, uplink := range *responseApplianceUplinkStatuses { - - if device, ok := devices[meraki.Serial(uplink.Serial)]; ok { - metric := mapstr.M{ - "appliance.uplink.high_availablity.enabled": uplink.HighAvailability.Enabled, - "appliance.uplink.high_availablity.role": uplink.HighAvailability.Role, - "appliance.uplink.last_reported_at": uplink.LastReportedAt, - "appliance.address": device.Address, - "appliance.firmware": device.Firmware, - "appliance.imei": device.Imei, - "appliance.lan_ip": device.LanIP, - "appliance.location": device.Location, - "appliance.mac": device.Mac, - "appliance.model": device.Model, - "appliance.name": device.Name, - "appliance.network_id": device.NetworkID, - "appliance.notes": device.Notes, - "appliance.product_type": device.ProductType, - "appliance.serial": device.Serial, - "appliance.tags": device.Tags, - } - - //Not sure if this is really needed on uplink status - // for k, v := range device.Details { - // metric[fmt.Sprintf("appliance.details.%s", k)] = v - // } - - for _, item := range *uplink.Uplinks { - metrics = append(metrics, mapstr.Union(metric, mapstr.M{ - "appliance.uplink.interface": item.Interface, - "appliance.uplink.status": item.Status, - "appliance.uplink.ip": item.IP, - "appliance.uplink.gateway": item.Gateway, - "appliance.uplink.public_ip": item.PublicIP, - "appliance.uplink.primary_dns": item.PrimaryDNS, - "appliance.uplink.secondary_dns": item.SecondaryDNS, - "appliance.uplink.ip_assigned_by": item.IPAssignedBy, - })) - - } - } - } - - meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) -} diff --git a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/data.json b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/data.json deleted file mode 100644 index 34914db44e2..00000000000 --- a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" - }, - "metricset":{ - "host":"localhost", - "module":"meraki", - "name":"cellular_gateway_uplink_status", - "rtt":44269 - }, - "meraki":{ - "cellular_gateway_uplink_status":{ - "example": "cellular_gateway_uplink_status" - } - }, - "type":"metricsets" -} diff --git a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/docs.asciidoc deleted file mode 100644 index bda6eb47a51..00000000000 --- a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the cellular_gateway_uplink_status metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/fields.yml b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/fields.yml deleted file mode 100644 index 4f43ea7ac64..00000000000 --- a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- name: cellular_gateway_uplink_status - type: group - release: beta - description: > - cellular_gateway_uplink_status - fields: - - name: example - type: keyword - description: > - Example field diff --git a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/cellular_gateway_uplink_status.go b/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/cellular_gateway_uplink_status.go deleted file mode 100644 index cfa3fd9d2db..00000000000 --- a/x-pack/metricbeat/module/meraki/cellular_gateway_uplink_status/cellular_gateway_uplink_status.go +++ /dev/null @@ -1,155 +0,0 @@ -package cellular_gateway_uplink_status - -import ( - "fmt" - - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - "github.com/elastic/elastic-agent-libs/logp" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - mb.Registry.MustAddMetricSet(meraki.ModuleName, "cellular_gateway_uplink_status", New) -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki cellular_gateway_uplink_status metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - }, nil -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - for _, org := range m.organizations { - - devices, err := meraki.GetDevices(m.client, org) - if err != nil { - return err - } - - val, res, err := m.client.CellularGateway.GetOrganizationCellularGatewayUplinkStatuses(org, &meraki_api.GetOrganizationCellularGatewayUplinkStatusesQueryParams{}) - if err != nil { - return fmt.Errorf("CellularGateway.GetOrganizationCellularGatewayUplinkStatuses failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - //fmt.Printf("CellularGateway.GetOrganizationCellularGatewayUplinkStatuses debug; [%d] %s.", res.StatusCode(), res.Body()) - - reportApplianceUplinkStatuses(reporter, org, devices, val) - } - - return nil -} - -func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, responseCellularGatewayUplinkStatuses *meraki_api.ResponseCellularGatewayGetOrganizationCellularGatewayUplinkStatuses) { - - metrics := []mapstr.M{} - - for _, uplink := range *responseCellularGatewayUplinkStatuses { - - if device, ok := devices[meraki.Serial(uplink.Serial)]; ok { - metric := mapstr.M{ - //this one should be deleted, I just want to see if it matches the device.network_id - "cellular.gateway.uplink.networkd_id": uplink.NetworkID, - "cellular.gateway.uplink.last_reported_at": uplink.LastReportedAt, - "cellular.gateway.address": device.Address, - "cellular.gateway.firmware": device.Firmware, - "cellular.gateway.imei": device.Imei, - "cellular.gateway.lan_ip": device.LanIP, - "cellular.gateway.location": device.Location, - "cellular.gateway.mac": device.Mac, - "cellular.gateway.model": device.Model, - "cellular.gateway.name": device.Name, - "cellular.gateway.network_id": device.NetworkID, - "cellular.gateway.notes": device.Notes, - "cellular.gateway.product_type": device.ProductType, - "cellular.gateway.serial": device.Serial, - "cellular.gateway.tags": device.Tags, - } - - //Not sure if this is really needed on uplink status - // for k, v := range device.Details { - // metric[fmt.Sprintf("cellular.gateway.details.%s", k)] = v - // } - - // #FIXME - Review might need to be like other uplinks - for i, item := range *uplink.Uplinks { - metrics = append(metrics, mapstr.Union(metric, mapstr.M{ - fmt.Sprintf("cellular.gateway.uplink.item_%d.apn", i): item.Apn, - fmt.Sprintf("cellular.gateway.uplink.item_%d.connection_type", i): item.ConnectionType, - fmt.Sprintf("cellular.gateway.uplink.item_%d.dns1", i): item.DNS1, - fmt.Sprintf("cellular.gateway.uplink.item_%d.dns2", i): item.DNS2, - fmt.Sprintf("cellular.gateway.uplink.item_%d.gateway", i): item.Gateway, - fmt.Sprintf("cellular.gateway.uplink.item_%d.iccid", i): item.Iccid, - fmt.Sprintf("cellular.gateway.uplink.item_%d.interface", i): item.Interface, - fmt.Sprintf("cellular.gateway.uplink.item_%d.ip", i): item.IP, - fmt.Sprintf("cellular.gateway.uplink.item_%d.model", i): item.Model, - fmt.Sprintf("cellular.gateway.uplink.item_%d.provider", i): item.Provider, - fmt.Sprintf("cellular.gateway.uplink.item_%d.public_ip", i): item.PublicIP, - fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_stat.rsrp", i): item.SignalStat.Rsrp, - fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_stat.rsrq", i): item.SignalStat.Rsrq, - fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_type", i): item.SignalType, - fmt.Sprintf("cellular.gateway.uplink.item_%d.status", i): item.Status, - })) - - } - } - } - meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) -} diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/data.json b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/data.json deleted file mode 100644 index 959d5682cba..00000000000 --- a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" - }, - "metricset":{ - "host":"localhost", - "module":"meraki", - "name":"device_appliance_performance_score", - "rtt":44269 - }, - "meraki":{ - "device_appliance_performance_score":{ - "example": "device_appliance_performance_score" - } - }, - "type":"metricsets" -} diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/docs.asciidoc deleted file mode 100644 index c5647ff9765..00000000000 --- a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the device_appliance_performance_score metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/fields.yml b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/fields.yml deleted file mode 100644 index c4e88ac8d14..00000000000 --- a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- name: device_appliance_performance_score - type: group - release: beta - description: > - device_appliance_performance_score - fields: - - name: example - type: keyword - description: > - Example field diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/device_appliance_performance_score.go b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/device_appliance_performance_score.go deleted file mode 100644 index d3c67c94f6a..00000000000 --- a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/device_appliance_performance_score.go +++ /dev/null @@ -1,198 +0,0 @@ -package device_appliance_performance_score - -import ( - "encoding/json" - "fmt" - "io" - "log" - "strings" - - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - "github.com/elastic/elastic-agent-libs/logp" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - mb.Registry.MustAddMetricSet(meraki.ModuleName, "device_appliance_performance_score", New) -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string - meraki_url string - meraki_apikey string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki device_appliance_performance_score metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - meraki_url: config.BaseURL, - meraki_apikey: config.ApiKey, - }, nil - -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - for _, org := range m.organizations { - - devices, err := meraki.GetDevices(m.client, org) - if err != nil { - return err - } - - mx_devices := pruneDevicesForMxOnly(devices) - mx_scores, _ := getDevicePerformanceScores(m.meraki_url, m.meraki_apikey, mx_devices) - - reportPerformanceScoreMetrics(reporter, org, mx_devices, mx_scores) - - } - - return nil -} - -func getDevicePerformanceScores(url string, token string, mx_devices map[meraki.Serial]*meraki.Device) (map[meraki.Serial]*DevicePerformanceScore, error) { - - scores := make(map[meraki.Serial]*DevicePerformanceScore) - for _, device := range mx_devices { - - perf_score, status_code := getDevicePerformanceScoresBySerialId(url, token, device.Serial) - - scores[meraki.Serial(device.Serial)] = &DevicePerformanceScore{ - PerformanceScore: perf_score, - HttpStatusCode: status_code, - } - } - - return scores, nil -} - -func pruneDevicesForMxOnly(devices map[meraki.Serial]*meraki.Device) map[meraki.Serial]*meraki.Device { - - mx_devices := make(map[meraki.Serial]*meraki.Device) - for k, v := range devices { - if strings.Index(v.Model, "MX") == 0 { - mx_devices[k] = v - } - } - return mx_devices -} - -func getDevicePerformanceScoresBySerialId(base_url string, token string, serial string) (float64, int) { - //https://developer.cisco.com/meraki/api-v1/get-device-appliance-performance/ - url := base_url + "/api/v1/devices/" + serial + "/appliance/performance" - - // NEED TO ADD RETRY LOGIC - //https://github.com/meraki/dashboard-api-go/blob/25b775d00e5c392677399e4fb1dfb0cfb67badce/sdk/api_client.go#L104C1-L123C3 - //https://developer.cisco.com/meraki/api-v1/rate-limit/#rate-limit - - response, err := meraki.HttpGetRequestWithMerakiRetry(url, token, 5) - - if err != nil { - log.Fatal(err) - } - - responseData, err := io.ReadAll(response.Body) - if err != nil { - log.Fatal(err) - } - - var responseObject PerfScore - json.Unmarshal(responseData, &responseObject) - - //var tmp_float float64 - tmp_float := responseObject.PerformanceScore - - return tmp_float, response.StatusCode - -} - -func reportPerformanceScoreMetrics(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, devicePerformanceScores map[meraki.Serial]*DevicePerformanceScore) { - devicePerformanceScoreMetrics := []mapstr.M{} - for serial, device := range devices { - metric := mapstr.M{ - "device.appliance.address": device.Address, - "device.appliance.firmware": device.Firmware, - "device.appliance.imei": device.Imei, - "device.appliance.lan_ip": device.LanIP, - "device.appliance.location": device.Location, - "device.appliance.mac": device.Mac, - "device.appliance.model": device.Model, - "device.appliance.name": device.Name, - "device.appliance.network_id": device.NetworkID, - "device.appliance.notes": device.Notes, - "device.appliance.product_type": device.ProductType, - "device.appliance.serial": device.Serial, - "device.appliance.tags": device.Tags, - } - - for k, v := range device.Details { - metric[fmt.Sprintf("device.appliance.details.%s", k)] = v - } - - if score, ok := devicePerformanceScores[serial]; ok { - if score.HttpStatusCode == 204 { - metric["device.appliance.performance.http_status_code"] = score.HttpStatusCode - } else { - metric["device.appliance.performance.score"] = score.PerformanceScore - } - - } - devicePerformanceScoreMetrics = append(devicePerformanceScoreMetrics, metric) - } - - meraki.ReportMetricsForOrganization(reporter, organizationID, devicePerformanceScoreMetrics) - -} diff --git a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/types.go b/x-pack/metricbeat/module/meraki/device_appliance_performance_score/types.go deleted file mode 100644 index 5ce78213f07..00000000000 --- a/x-pack/metricbeat/module/meraki/device_appliance_performance_score/types.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package device_appliance_performance_score - -type PerfScore struct { - PerformanceScore float64 `json:"perfScore"` -} - -// DeviceStatus contains dynamic device attributes -type DevicePerformanceScore struct { - PerformanceScore float64 - HttpStatusCode int -} diff --git a/x-pack/metricbeat/module/meraki/device_status/_meta/data.json b/x-pack/metricbeat/module/meraki/device_health/_meta/data.json similarity index 74% rename from x-pack/metricbeat/module/meraki/device_status/_meta/data.json rename to x-pack/metricbeat/module/meraki/device_health/_meta/data.json index 3f7d55cd174..23f3ffcaf15 100644 --- a/x-pack/metricbeat/module/meraki/device_status/_meta/data.json +++ b/x-pack/metricbeat/module/meraki/device_health/_meta/data.json @@ -7,12 +7,12 @@ "metricset":{ "host":"localhost", "module":"meraki", - "name":"device_status", + "name":"device_health", "rtt":44269 }, "meraki":{ - "device_status":{ - "example": "device_status" + "device_health":{ + "example": "device_health" } }, "type":"metricsets" diff --git a/x-pack/metricbeat/module/meraki/device_health/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/device_health/_meta/docs.asciidoc new file mode 100644 index 00000000000..f09d386197a --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the device_health metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/device_status/_meta/fields.yml b/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml similarity index 77% rename from x-pack/metricbeat/module/meraki/device_status/_meta/fields.yml rename to x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml index e245f90f4f2..b0e83e2959d 100644 --- a/x-pack/metricbeat/module/meraki/device_status/_meta/fields.yml +++ b/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml @@ -1,8 +1,8 @@ -- name: device_status +- name: device_health type: group release: beta description: > - device_status + device_health fields: - name: example type: keyword diff --git a/x-pack/metricbeat/module/meraki/device_health/appliance_uplink_status_and_ha.go b/x-pack/metricbeat/module/meraki/device_health/appliance_uplink_status_and_ha.go new file mode 100644 index 00000000000..3fde8e65748 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/appliance_uplink_status_and_ha.go @@ -0,0 +1,53 @@ +package device_health + +import ( + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseApplianceUplinkStatuses *meraki_api.ResponseApplianceGetOrganizationApplianceUplinkStatuses) { + + metrics := []mapstr.M{} + + for _, uplink := range *responseApplianceUplinkStatuses { + + if device, ok := devices[Serial(uplink.Serial)]; ok { + metric := mapstr.M{ + "appliance.uplink.high_availablity.enabled": uplink.HighAvailability.Enabled, + "appliance.uplink.high_availablity.role": uplink.HighAvailability.Role, + "appliance.uplink.last_reported_at": uplink.LastReportedAt, + "appliance.address": device.Address, + "appliance.firmware": device.Firmware, + "appliance.imei": device.Imei, + "appliance.lan_ip": device.LanIP, + "appliance.location": device.Location, + "appliance.mac": device.Mac, + "appliance.model": device.Model, + "appliance.name": device.Name, + "appliance.network_id": device.NetworkID, + "appliance.notes": device.Notes, + "appliance.product_type": device.ProductType, + "appliance.serial": device.Serial, + "appliance.tags": device.Tags, + } + + for _, item := range *uplink.Uplinks { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "appliance.uplink.interface": item.Interface, + "appliance.uplink.status": item.Status, + "appliance.uplink.ip": item.IP, + "appliance.uplink.gateway": item.Gateway, + "appliance.uplink.public_ip": item.PublicIP, + "appliance.uplink.primary_dns": item.PrimaryDNS, + "appliance.uplink.secondary_dns": item.SecondaryDNS, + "appliance.uplink.ip_assigned_by": item.IPAssignedBy, + })) + + } + } + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/cellular_gateway_uplink_status.go b/x-pack/metricbeat/module/meraki/device_health/cellular_gateway_uplink_status.go new file mode 100644 index 00000000000..653597a5ace --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/cellular_gateway_uplink_status.go @@ -0,0 +1,60 @@ +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportCellularGatewayApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseCellularGatewayUplinkStatuses *meraki_api.ResponseCellularGatewayGetOrganizationCellularGatewayUplinkStatuses) { + + metrics := []mapstr.M{} + + for _, uplink := range *responseCellularGatewayUplinkStatuses { + + if device, ok := devices[Serial(uplink.Serial)]; ok { + metric := mapstr.M{ + "cellular.gateway.uplink.network_id": uplink.NetworkID, + "cellular.gateway.uplink.last_reported_at": uplink.LastReportedAt, + "cellular.gateway.address": device.Address, + "cellular.gateway.firmware": device.Firmware, + "cellular.gateway.imei": device.Imei, + "cellular.gateway.lan_ip": device.LanIP, + "cellular.gateway.location": device.Location, + "cellular.gateway.mac": device.Mac, + "cellular.gateway.model": device.Model, + "cellular.gateway.name": device.Name, + "cellular.gateway.network_id": device.NetworkID, + "cellular.gateway.notes": device.Notes, + "cellular.gateway.product_type": device.ProductType, + "cellular.gateway.serial": device.Serial, + "cellular.gateway.tags": device.Tags, + } + + for i, item := range *uplink.Uplinks { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + fmt.Sprintf("cellular.gateway.uplink.item_%d.apn", i): item.Apn, + fmt.Sprintf("cellular.gateway.uplink.item_%d.connection_type", i): item.ConnectionType, + fmt.Sprintf("cellular.gateway.uplink.item_%d.dns1", i): item.DNS1, + fmt.Sprintf("cellular.gateway.uplink.item_%d.dns2", i): item.DNS2, + fmt.Sprintf("cellular.gateway.uplink.item_%d.gateway", i): item.Gateway, + fmt.Sprintf("cellular.gateway.uplink.item_%d.iccid", i): item.Iccid, + fmt.Sprintf("cellular.gateway.uplink.item_%d.interface", i): item.Interface, + fmt.Sprintf("cellular.gateway.uplink.item_%d.ip", i): item.IP, + fmt.Sprintf("cellular.gateway.uplink.item_%d.model", i): item.Model, + fmt.Sprintf("cellular.gateway.uplink.item_%d.provider", i): item.Provider, + fmt.Sprintf("cellular.gateway.uplink.item_%d.public_ip", i): item.PublicIP, + fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_stat.rsrp", i): item.SignalStat.Rsrp, + fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_stat.rsrq", i): item.SignalStat.Rsrq, + fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_type", i): item.SignalType, + fmt.Sprintf("cellular.gateway.uplink.item_%d.status", i): item.Status, + })) + + } + } + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go new file mode 100644 index 00000000000..71702c5b886 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go @@ -0,0 +1,114 @@ +package device_health + +import ( + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" +) + +func getDevicePerformanceScores(url string, token string, devices map[Serial]*Device) (map[Serial]*Device, map[Serial]*DevicePerformanceScore, error) { + + mx_devices := pruneDevicesForMxOnly(devices) + + scores := make(map[Serial]*DevicePerformanceScore) + for _, device := range mx_devices { + + perf_score, status_code, err := getDevicePerformanceScoresBySerialId(url, token, device.Serial) + if err != nil { + return nil, nil, fmt.Errorf("getDevicePerformanceScores -> getDevicePerformanceScoresBySerialId failed; %w", err) + } + + scores[Serial(device.Serial)] = &DevicePerformanceScore{ + PerformanceScore: perf_score, + HttpStatusCode: status_code, + } + } + + return mx_devices, scores, nil +} + +func pruneDevicesForMxOnly(devices map[Serial]*Device) map[Serial]*Device { + + mx_devices := make(map[Serial]*Device) + for k, v := range devices { + if strings.Index(v.Model, "MX") == 0 { + mx_devices[k] = v + } + } + return mx_devices +} + +func getDevicePerformanceScoresBySerialId(base_url string, token string, serial string) (float64, int, error) { + //https://developer.cisco.com/meraki/api-v1/get-device-appliance-performance/ + url := base_url + "/api/v1/devices/" + serial + "/appliance/performance" + + response, err := HttpGetRequestWithMerakiRetry(url, token, 5) + if err != nil { + return 0, 0, fmt.Errorf("getDevicePerformanceScoresBySerialId HttpGetRequestWithMerakiRetry failed; %w", err) + } + + if response.StatusCode == 204 { + return -1, response.StatusCode, nil + } + + responseData, err := io.ReadAll(response.Body) + if err != nil { + return 0, 0, fmt.Errorf("getDevicePerformanceScoresBySerialId io.ReadAll failed; %w", err) + } + + var responseObject PerfScore + err = json.Unmarshal(responseData, &responseObject) + if err != nil { + fmt.Printf("\nresponse.status=%d \nresponse.body=%s", response.StatusCode, response.Body) + fmt.Printf("\nresponseData\n %s", responseData) + return 0, 0, fmt.Errorf("getDevicePerformanceScoresBySerialId json.Unmarshal failed; %w", err) + } + + //var tmp_float float64 + tmp_float := responseObject.PerformanceScore + + return tmp_float, response.StatusCode, nil + +} + +func reportPerformanceScoreMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, devicePerformanceScores map[Serial]*DevicePerformanceScore) { + devicePerformanceScoreMetrics := []mapstr.M{} + for serial, device := range devices { + metric := mapstr.M{ + "device.appliance.address": device.Address, + "device.appliance.firmware": device.Firmware, + "device.appliance.imei": device.Imei, + "device.appliance.lan_ip": device.LanIP, + "device.appliance.location": device.Location, + "device.appliance.mac": device.Mac, + "device.appliance.model": device.Model, + "device.appliance.name": device.Name, + "device.appliance.network_id": device.NetworkID, + "device.appliance.notes": device.Notes, + "device.appliance.product_type": device.ProductType, + "device.appliance.serial": device.Serial, + "device.appliance.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.appliance.details.%s", k)] = v + } + + if score, ok := devicePerformanceScores[serial]; ok { + if score.HttpStatusCode == 204 { + metric["device.appliance.performance.http_status_code"] = score.HttpStatusCode + } else { + metric["device.appliance.performance.score"] = score.PerformanceScore + } + + } + devicePerformanceScoreMetrics = append(devicePerformanceScoreMetrics, metric) + } + + ReportMetricsForOrganization(reporter, organizationID, devicePerformanceScoreMetrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go new file mode 100644 index 00000000000..697c0ee6677 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -0,0 +1,222 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strconv" + "time" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" + + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host is defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + + mb.Registry.MustAddMetricSet("meraki", "device_health", New) + +} + +type config struct { + BaseURL string `config:"apiBaseURL"` + ApiKey string `config:"apiKey"` + DebugMode string `config:"apiDebugMode"` + Organizations []string `config:"organizations"` + // todo: device filtering? +} + +func defaultConfig() *config { + return &config{ + BaseURL: "https://api.meraki.com", + DebugMode: "false", + } +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + logger *logp.Logger + client *meraki_api.Client + organizations []string + meraki_url string + meraki_apikey string +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The meraki device_health metricset is beta.") + + logger := logp.NewLogger(base.FullyQualifiedName()) + + config := defaultConfig() + if err := base.Module().UnpackConfig(config); err != nil { + return nil, err + } + + logger.Debugf("loaded config: %v", config) + client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") + if err != nil { + logger.Error("creating Meraki dashboard API client failed: %w", err) + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + logger: logger, + client: client, + organizations: config.Organizations, + meraki_url: config.BaseURL, + meraki_apikey: config.ApiKey, + }, nil +} + +// Fetch method implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { + + for _, org := range m.organizations { + + //Get Devices + devices, err := GetDevices(m.client, org) + if err != nil { + return fmt.Errorf("getDevices() failed; %w", err) + } + + //Get & Report Device Status + deviceStatuses, err := getDeviceStatuses(m.client, org) + if err != nil { + return fmt.Errorf("getDeviceStatuses() failed; %w", err) + } + reportDeviceStatusMetrics(reporter, org, devices, deviceStatuses) + + //Get & Report Device Uplink Status + uplinks, err := getDeviceUplinkMetrics(m.client, org, m.BaseMetricSet.Module().Config().Period) + if err != nil { + return fmt.Errorf("getDeviceUplinkMetrics() failed; %w", err) + } + reportDeviceUplinkMetrics(reporter, org, devices, uplinks) + + //Get & Report Device License State + cotermLicenses, perDeviceLicenses, systemsManagerLicense, err := getLicenseStates(m.client, org) + if err != nil { + return fmt.Errorf("getLicenseStates() failed; %w", err) + } + reportLicenseMetrics(reporter, org, cotermLicenses, perDeviceLicenses, systemsManagerLicense) + + //Get mx device performance score + mx_devices, mx_scores, err := getDevicePerformanceScores(m.meraki_url, m.meraki_apikey, devices) + if err != nil { + return fmt.Errorf("getDevicePerformanceScores() failed; %w", err) + } + reportPerformanceScoreMetrics(reporter, org, mx_devices, mx_scores) + + //Get & Report Org Celluar Uplink Status + cullular_val, cullular_res, cullular_err := m.client.CellularGateway.GetOrganizationCellularGatewayUplinkStatuses(org, &meraki_api.GetOrganizationCellularGatewayUplinkStatusesQueryParams{}) + if cullular_err != nil { + return fmt.Errorf("CellularGateway.GetOrganizationCellularGatewayUplinkStatuses failed; [%d] %s. %w", cullular_res.StatusCode(), cullular_res.Body(), cullular_err) + } + reportCellularGatewayApplianceUplinkStatuses(reporter, org, devices, cullular_val) + + //Get & Report Organization Appliance Uplink + appliance_val, appliance_res, appliance_err := m.client.Appliance.GetOrganizationApplianceUplinkStatuses(org, &meraki_api.GetOrganizationApplianceUplinkStatusesQueryParams{}) + if appliance_err != nil { + return fmt.Errorf("Appliance.GetOrganizationApplianceUplinkStatuses failed; [%d] %s. %w", appliance_res.StatusCode(), appliance_res.Body(), appliance_err) + } + reportApplianceUplinkStatuses(reporter, org, devices, appliance_val) + + //Get Org Networks + //Get Network Health by Org Network + //Report NetworkHalthChannelUtilization + orgNetworks, orgNetwork_res, orgNetwork_err := m.client.Organizations.GetOrganizationNetworks(org, &meraki_api.GetOrganizationNetworksQueryParams{}) + if orgNetwork_err != nil { + return fmt.Errorf("Organizations.GetOrganizationNetworks failed; [%d] %s. %w", orgNetwork_res.StatusCode(), orgNetwork_res.Body(), orgNetwork_err) + } + networkHealthUtilizations, err := getNetworkHealthChannelUtilization(m.client, orgNetworks) + if err != nil { + return err + } + reportNetworkHealthChannelUtilization(reporter, org, devices, networkHealthUtilizations) + + // Get and Report Organization Wireless Devices Channel Utilization + wireless_res, wireless_err := m.client.Devices.GetOrganizationWirelessDevicesChannelUtilizationByDevice(org, &meraki_api.GetOrganizationWirelessDevicesChannelUtilizationByDeviceQueryParams{}) + if wireless_err != nil { + return fmt.Errorf("GetOrganizationWirelessDevicesChannelUtilizationByDevice failed; [%d] %s. %w", wireless_res.StatusCode(), wireless_res.Body(), wireless_err) + } + var wirelessDevices WirelessDevicesChannelUtilizationByDevice + unmashal_err := json.Unmarshal(wireless_res.Body(), &wirelessDevices) + if unmashal_err != nil { + return fmt.Errorf("device_network_health_channel_utilization json umarshal failed; %w", unmashal_err) + } + reportWirelessDeviceChannelUtilization(reporter, org, devices, wirelessDevices) + + } + + return nil +} + +func ReportMetricsForOrganization(reporter mb.ReporterV2, organizationID string, metrics ...[]mapstr.M) { + + for _, metricSlice := range metrics { + for _, metric := range metricSlice { + event := mb.Event{ModuleFields: mapstr.M{"organization_id": organizationID}} + if ts, ok := metric["@timestamp"].(time.Time); ok { + event.Timestamp = ts + delete(metric, "@timestamp") + } + event.ModuleFields.Update(metric) + reporter.Event(event) + } + } +} + +func HttpGetRequestWithMerakiRetry(url string, token string, retry int) (*http.Response, error) { + //https://developer.cisco.com/meraki/api-v1/get-device-appliance-performance/ + + // Create a Bearer string by appending string access token + var bearer = "Bearer " + token + + // Create a new request using http + req, _ := http.NewRequest("GET", url, nil) + + // add authorization header to the req + req.Header.Add("Authorization", bearer) + req.Header.Add("Accept", "application/json") + + client := &http.Client{} + response, err := client.Do(req) + + // Rate Limt Retry After Needed due to only 10 requests per second allowed by API + //https://developer.cisco.com/meraki/api-v1/rate-limit/#rate-limit + for i := 0; i < retry && response.StatusCode == 429; i++ { + + retryHeader := response.Header.Get("Retry-After") + if _, err := strconv.Atoi(retryHeader); err == nil { + log.Printf("Retry Limit Paused for %s second", retryHeader) + time.ParseDuration(retryHeader + "s") + } + response, err = client.Do(req) + + } + + return response, err + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_info.go b/x-pack/metricbeat/module/meraki/device_health/device_info.go new file mode 100644 index 00000000000..bd6b69e5337 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_info.go @@ -0,0 +1,81 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "fmt" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func GetDevicesByProductType(client *meraki_api.Client, organizationID string, productTypes []string) (map[Serial]*Device, error) { + val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{ + ProductTypes: productTypes, + }) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevices failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + devices := make(map[Serial]*Device) + for _, d := range *val { + device := Device{ + Firmware: d.Firmware, + Imei: d.Imei, + LanIP: d.LanIP, + Location: []*float64{d.Lng, d.Lat}, // (lon, lat) order is important! + Mac: d.Mac, + Model: d.Model, + Name: d.Name, + NetworkID: d.NetworkID, + Notes: d.Notes, + ProductType: d.ProductType, + Serial: d.Serial, + Tags: d.Tags, + } + if d.Details != nil { + for _, detail := range *d.Details { + device.Details[detail.Name] = detail.Value + } + } + devices[Serial(device.Serial)] = &device + } + + return devices, nil +} + +func GetDevices(client *meraki_api.Client, organizationID string) (map[Serial]*Device, error) { + val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{}) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevices failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + devices := make(map[Serial]*Device) + for _, d := range *val { + device := Device{ + Firmware: d.Firmware, + Imei: d.Imei, + LanIP: d.LanIP, + Location: []*float64{d.Lng, d.Lat}, // (lon, lat) order is important! + Mac: d.Mac, + Model: d.Model, + Name: d.Name, + NetworkID: d.NetworkID, + Notes: d.Notes, + ProductType: d.ProductType, + Serial: d.Serial, + Tags: d.Tags, + } + if d.Details != nil { + for _, detail := range *d.Details { + device.Details[detail.Name] = detail.Value + } + } + devices[Serial(device.Serial)] = &device + } + + return devices, nil +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_status.go b/x-pack/metricbeat/module/meraki/device_health/device_status.go new file mode 100644 index 00000000000..c076933cefd --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_status.go @@ -0,0 +1,75 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getDeviceStatuses(client *meraki_api.Client, organizationID string) (map[Serial]*DeviceStatus, error) { + val, res, err := client.Organizations.GetOrganizationDevicesStatuses(organizationID, &meraki_api.GetOrganizationDevicesStatusesQueryParams{}) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevicesStatuses failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + statuses := make(map[Serial]*DeviceStatus) + for _, status := range *val { + statuses[Serial(status.Serial)] = &DeviceStatus{ + Gateway: status.Gateway, + IPType: status.IPType, + LastReportedAt: status.LastReportedAt, + PrimaryDNS: status.PrimaryDNS, + PublicIP: status.PublicIP, + SecondaryDNS: status.SecondaryDNS, + Status: status.Status, + } + } + + return statuses, nil +} + +func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, deviceStatuses map[Serial]*DeviceStatus) { + deviceStatusMetrics := []mapstr.M{} + for serial, device := range devices { + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v + } + + if status, ok := deviceStatuses[serial]; ok { + metric["device.status.gateway"] = status.Gateway + metric["device.status.ip_type"] = status.IPType + metric["device.status.last_reported_at"] = status.LastReportedAt + metric["device.status.primary_dns"] = status.PrimaryDNS + metric["device.status.public_ip"] = status.PublicIP + metric["device.status.secondary_dns"] = status.SecondaryDNS + metric["device.status.status"] = status.Status + } + deviceStatusMetrics = append(deviceStatusMetrics, metric) + } + + ReportMetricsForOrganization(reporter, organizationID, deviceStatusMetrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go new file mode 100644 index 00000000000..d36b7a2bb6e --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go @@ -0,0 +1,104 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "fmt" + "time" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getDeviceUplinkMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { + val, res, err := client.Organizations.GetOrganizationDevicesUplinksLossAndLatency( + organizationID, + &meraki_api.GetOrganizationDevicesUplinksLossAndLatencyQueryParams{ + Timespan: period.Seconds() + 10, // slightly longer than the fetch period to ensure we don't miss measurements due to jitter + }, + ) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevicesUplinksLossAndLatency failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + var uplinks []*Uplink + + for _, device := range *val { + uplink := &Uplink{ + DeviceSerial: Serial(device.Serial), + IP: device.IP, + Interface: device.Uplink, + } + + for _, measurement := range *device.TimeSeries { + if measurement.LossPercent != nil || measurement.LatencyMs != nil { + timestamp, err := time.Parse(time.RFC3339, measurement.Ts) + if err != nil { + return nil, fmt.Errorf("failed to parse timestamp [%s] in ResponseOrganizationsGetOrganizationDevicesUplinksLossAndLatency: %w", measurement.Ts, err) + } + + metric := UplinkMetric{Timestamp: timestamp} + if measurement.LossPercent != nil { + metric.LossPercent = measurement.LossPercent + } + if measurement.LatencyMs != nil { + metric.LatencyMs = measurement.LatencyMs + } + uplink.Metrics = append(uplink.Metrics, &metric) + } + } + + if len(uplink.Metrics) != 0 { + uplinks = append(uplinks, uplink) + } + } + + return uplinks, nil +} + +func reportDeviceUplinkMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, uplinks []*Uplink) { + metrics := []mapstr.M{} + + for _, uplink := range uplinks { + if device, ok := devices[uplink.DeviceSerial]; ok { + metric := mapstr.M{ + "uplink.ip": uplink.IP, + "uplink.interface": uplink.Interface, + // fixme: repeated code serializing device metadata to mapstr + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v + } + + for _, uplinkMetric := range uplink.Metrics { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "@timestamp": uplinkMetric.Timestamp, + "uplink.loss_percent": uplinkMetric.LossPercent, + "uplink.latency_ms": uplinkMetric.LatencyMs, + })) + } + } else { + // missing device metadata; ignore + } + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/license_overview/license_overview.go b/x-pack/metricbeat/module/meraki/device_health/license_overview.go similarity index 66% rename from x-pack/metricbeat/module/meraki/license_overview/license_overview.go rename to x-pack/metricbeat/module/meraki/device_health/license_overview.go index 52a3328b916..6e39d6a2951 100644 --- a/x-pack/metricbeat/module/meraki/license_overview/license_overview.go +++ b/x-pack/metricbeat/module/meraki/device_health/license_overview.go @@ -1,95 +1,14 @@ -package license_overview +package device_health import ( "fmt" - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/elastic-agent-libs/mapstr" meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - mb.Registry.MustAddMetricSet(meraki.ModuleName, "license_overview", New) -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki license_overview metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - }, nil -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - for _, org := range m.organizations { - - cotermLicenses, perDeviceLicenses, systemsManagerLicense, err := getLicenseStates(m.client, org) - if err != nil { - return err - } - - reportLicenseMetrics(reporter, org, cotermLicenses, perDeviceLicenses, systemsManagerLicense) - } - - return nil -} - func getLicenseStates(client *meraki_api.Client, organizationID string) ([]*CoterminationLicense, []*PerDeviceLicense, *SystemsManagerLicense, error) { val, res, err := client.Organizations.GetOrganizationLicensesOverview(organizationID) @@ -210,7 +129,7 @@ func reportLicenseMetrics(reporter mb.ReporterV2, organizationID string, cotermL "license.count": license.Count, }) } - meraki.ReportMetricsForOrganization(reporter, organizationID, cotermLicenseMetrics) + ReportMetricsForOrganization(reporter, organizationID, cotermLicenseMetrics) } if len(perDeviceLicenses) != 0 { @@ -228,12 +147,12 @@ func reportLicenseMetrics(reporter mb.ReporterV2, organizationID string, cotermL "license.type": license.Type, }) } - meraki.ReportMetricsForOrganization(reporter, organizationID, perDeviceLicenseMetrics) + ReportMetricsForOrganization(reporter, organizationID, perDeviceLicenseMetrics) } if systemsManagerLicense != nil { - meraki.ReportMetricsForOrganization(reporter, organizationID, []mapstr.M{ + ReportMetricsForOrganization(reporter, organizationID, []mapstr.M{ { "license.systems_manager.active_seats": systemsManagerLicense.ActiveSeats, "license.systems_manager.orgwideenrolled_devices": systemsManagerLicense.OrgwideEnrolledDevices, diff --git a/x-pack/metricbeat/module/meraki/device_health/network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/network_health_channel_utilization.go new file mode 100644 index 00000000000..9998edaacdc --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/network_health_channel_utilization.go @@ -0,0 +1,88 @@ +package device_health + +import ( + "fmt" + "strings" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) ([]*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization, error) { + + var networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization + + for _, network := range *networks { + + for _, product_type := range network.ProductTypes { + + if strings.Compare(product_type, "wireless") == 0 { + networkHealthUtilization, res, err := client.Networks.GetNetworkNetworkHealthChannelUtilization(network.ID, &meraki_api.GetNetworkNetworkHealthChannelUtilizationQueryParams{}) + if err != nil { + //"This endpoint is only available for networks on MR 27.0 or above." + // We just swallow this error but do not append to the list + if !(strings.Contains(string(res.Body()), "MR 27.0")) { + //Any other problem we are going to return an error + return nil, fmt.Errorf("Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + } else { + networkHealthUtilizations = append(networkHealthUtilizations, networkHealthUtilization) + } + + } + } + } + + return networkHealthUtilizations, nil +} + +func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization) { + metrics := []mapstr.M{} + for _, networkHealthUtil := range networkHealthUtilizations { + for _, network := range *networkHealthUtil { + + metric := mapstr.M{ + "network.health.channel.radio.serial": network.Serial, + "network.health.channel.radio.model": network.Model, + "network.health.channel.radio.tags": network.Tags, + } + + for _, wifi0 := range *network.Wifi0 { + metric["network.health.channel.radio.wifi0.start_time"] = wifi0.StartTime + metric["network.health.channel.radio.wifi0.end_time"] = wifi0.EndTime + metric["network.health.channel.radio.wifi0.utilization80211"] = wifi0.Utilization80211 + metric["network.health.channel.radio.wifi0.utilizationNon80211"] = wifi0.UtilizationNon80211 + metric["network.health.channel.radio.wifi0.utilizationTotal"] = wifi0.UtilizationTotal + } + + for _, wifi1 := range *network.Wifi1 { + metric["network.health.channel.radio.wifi1.start_time"] = wifi1.StartTime + metric["network.health.channel.radio.wifi1.end_time"] = wifi1.EndTime + metric["network.health.channel.radio.wifi1.utilization80211"] = wifi1.Utilization80211 + metric["network.health.channel.radio.wifi1.utilizationNon80211"] = wifi1.UtilizationNon80211 + metric["network.health.channel.radio.wifi1.utilizationTotal"] = wifi1.UtilizationTotal + } + + if device, ok := devices[Serial(network.Serial)]; ok { + metric["device.address"] = device.Address + metric["device.firmware"] = device.Firmware + metric["device.imei"] = device.Imei + metric["device.lan_ip"] = device.LanIP + metric["device.location"] = device.Location + metric["device.mac"] = device.Mac + metric["device.model"] = device.Model + metric["device.name"] = device.Name + metric["device.network_id"] = device.NetworkID + metric["device.notes"] = device.Notes + metric["device.product_type"] = device.ProductType + metric["device.serial"] = device.Serial + metric["device.tags"] = device.Tags + + } + metrics = append(metrics, metric) + } + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/types.go b/x-pack/metricbeat/module/meraki/device_health/types.go new file mode 100644 index 00000000000..32a4c1530b5 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/types.go @@ -0,0 +1,116 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package device_health + +import ( + "time" +) + +// device unique identifier +type Serial string + +// Device contains static device attributes (i.e. dimensions) +type Device struct { + Address string + Details map[string]string + Firmware string + Imei *float64 + LanIP string + Location []*float64 + Mac string + Model string + Name string + NetworkID string + Notes string + ProductType string // one of ["appliance", "camera", "cellularGateway", "secureConnect", "sensor", "switch", "systemsManager", "wireless", "wirelessController"] + Serial string + Tags []string +} + +// DeviceStatus contains dynamic device attributes +type DeviceStatus struct { + Gateway string + IPType string + LastReportedAt string + PrimaryDNS string + PublicIP string + SecondaryDNS string + Status string // one of ["online", "alerting", "offline", "dormant"] +} + +// Uplink contains static device uplink attributes; uplinks are always associated with a device +type Uplink struct { + DeviceSerial Serial + IP string + Interface string + Metrics []*UplinkMetric +} + +// UplinkMetric contains timestamped device uplink metric data points +type UplinkMetric struct { + Timestamp time.Time + LossPercent *float64 + LatencyMs *float64 +} + +// CoterminationLicense have a common expiration date and are reported per device model +type CoterminationLicense struct { + ExpirationDate string + DeviceModel string + Status string + Count interface{} +} + +// PerDeviceLicense are reported by license state with details on expiration and activations +type PerDeviceLicense struct { + State string // one of ["Active", "Expired", "RecentlyQueued", "Expiring", "Unused", "UnusedActive", "Unassigned"] + Count *int + ExpirationState string // one of ["critial", "warning"] (only for Expiring licenses) + ExpirationThresholdDays *int // only for Expiring licenses + SoonestActivationDate string // only for Unused licenses + SoonestActivationCount *int // only for Unused licenses + OldestActivationDate string // only for UnusedActive licenses + OldestActivationCount *int // only for UnusedActive licenses + Type string // one of ["ENT", "UPGR", "ADV"] (only for Unassigned licenses) +} + +// SystemManagerLicense reports counts for seats and devices +type SystemsManagerLicense struct { + ActiveSeats *int + OrgwideEnrolledDevices *int + TotalSeats *int + UnassignedSeats *int +} + +type PerfScore struct { + PerformanceScore float64 `json:"perfScore"` +} + +// DeviceStatus contains dynamic device attributes +type DevicePerformanceScore struct { + PerformanceScore float64 + HttpStatusCode int +} + +// Uplink contains static device uplink attributes; uplinks are always associated with a device +type WirelessDevicesChannelUtilizationByDevice []struct { + Serial Serial `json:"serial"` + Mac string `json:"mac"` + Network struct { + ID string `json:"id"` + } `json:"network"` + ByBand []struct { + Band string `json:"band"` + Wifi struct { + Percentage float64 `json:"percentage"` + } `json:"wifi"` + NonWifi struct { + Percentage float64 `json:"percentage"` + } `json:"nonWifi"` + Total struct { + Percentage float64 `json:"percentage"` + } `json:"total"` + } `json:"byBand"` +} diff --git a/x-pack/metricbeat/module/meraki/device_health/wireless_device_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/wireless_device_channel_utilization.go new file mode 100644 index 00000000000..4b6f8e6bc63 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/wireless_device_channel_utilization.go @@ -0,0 +1,46 @@ +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" +) + +func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, wirelessDevices WirelessDevicesChannelUtilizationByDevice) { + + metrics := []mapstr.M{} + + for _, wirelessDevice := range wirelessDevices { + + if device, ok := devices[Serial(wirelessDevice.Serial)]; ok { + + metric := mapstr.M{ + "wireless.device.address": device.Address, + "wireless.device.firmware": device.Firmware, + "wireless.device.imei": device.Imei, + "wireless.device.lan_ip": device.LanIP, + "wireless.device.location": device.Location, + "wireless.device.mac": device.Mac, + "wireless.device.model": device.Model, + "wireless.device.name": device.Name, + "wireless.device.network_id": device.NetworkID, + "wireless.device.notes": device.Notes, + "wireless.device.product_type": device.ProductType, + "wireless.device.serial": device.Serial, + "wireless.device.tags": device.Tags, + } + + for _, v := range wirelessDevice.ByBand { + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.wifi.percentage", v.Band)] = v.Wifi.Percentage + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.nonwifi.percentage", v.Band)] = v.NonWifi.Percentage + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.total.percentage", v.Band)] = v.Total.Percentage + } + + metrics = append(metrics, metric) + + } + + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_status/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/device_status/_meta/docs.asciidoc deleted file mode 100644 index 59cb1786427..00000000000 --- a/x-pack/metricbeat/module/meraki/device_status/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the device_status metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/device_status/device_status.go b/x-pack/metricbeat/module/meraki/device_status/device_status.go deleted file mode 100644 index ddae555ddc6..00000000000 --- a/x-pack/metricbeat/module/meraki/device_status/device_status.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package device_status - -import ( - "fmt" - - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - - "github.com/elastic/elastic-agent-libs/logp" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - - mb.Registry.MustAddMetricSet(meraki.ModuleName, "device_status", New) - -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki device_status metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - }, nil -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - - for _, org := range m.organizations { - //devices, err := getDevices(m.client, org) - devices, err := meraki.GetDevices(m.client, org) - if err != nil { - return err - } - - deviceStatuses, err := getDeviceStatuses(m.client, org) - if err != nil { - return err - } - - reportDeviceStatusMetrics(reporter, org, devices, deviceStatuses) - - } - - return nil -} - -func getDeviceStatuses(client *meraki_api.Client, organizationID string) (map[meraki.Serial]*DeviceStatus, error) { - val, res, err := client.Organizations.GetOrganizationDevicesStatuses(organizationID, &meraki_api.GetOrganizationDevicesStatusesQueryParams{}) - - if err != nil { - return nil, fmt.Errorf("GetOrganizationDevicesStatuses failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - statuses := make(map[meraki.Serial]*DeviceStatus) - for _, status := range *val { - statuses[meraki.Serial(status.Serial)] = &DeviceStatus{ - Gateway: status.Gateway, - IPType: status.IPType, - LastReportedAt: status.LastReportedAt, - PrimaryDNS: status.PrimaryDNS, - PublicIP: status.PublicIP, - SecondaryDNS: status.SecondaryDNS, - Status: status.Status, - } - } - - return statuses, nil -} - -func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, deviceStatuses map[meraki.Serial]*DeviceStatus) { - deviceStatusMetrics := []mapstr.M{} - for serial, device := range devices { - metric := mapstr.M{ - "device.address": device.Address, - "device.firmware": device.Firmware, - "device.imei": device.Imei, - "device.lan_ip": device.LanIP, - "device.location": device.Location, - "device.mac": device.Mac, - "device.model": device.Model, - "device.name": device.Name, - "device.network_id": device.NetworkID, - "device.notes": device.Notes, - "device.product_type": device.ProductType, - "device.serial": device.Serial, - "device.tags": device.Tags, - } - - for k, v := range device.Details { - metric[fmt.Sprintf("device.details.%s", k)] = v - } - - if status, ok := deviceStatuses[serial]; ok { - metric["device.status.gateway"] = status.Gateway - metric["device.status.ip_type"] = status.IPType - metric["device.status.last_reported_at"] = status.LastReportedAt - metric["device.status.primary_dns"] = status.PrimaryDNS - metric["device.status.public_ip"] = status.PublicIP - metric["device.status.secondary_dns"] = status.SecondaryDNS - metric["device.status.status"] = status.Status - } - deviceStatusMetrics = append(deviceStatusMetrics, metric) - } - - meraki.ReportMetricsForOrganization(reporter, organizationID, deviceStatusMetrics) - -} diff --git a/x-pack/metricbeat/module/meraki/device_status/types.go b/x-pack/metricbeat/module/meraki/device_status/types.go deleted file mode 100644 index 600eef17eb9..00000000000 --- a/x-pack/metricbeat/module/meraki/device_status/types.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package device_status - -// DeviceStatus contains dynamic device attributes -type DeviceStatus struct { - Gateway string - IPType string - LastReportedAt string - PrimaryDNS string - PublicIP string - SecondaryDNS string - Status string // one of ["online", "alerting", "offline", "dormant"] -} diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/data.json b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/data.json deleted file mode 100644 index 41778cb4728..00000000000 --- a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" - }, - "metricset":{ - "host":"localhost", - "module":"meraki", - "name":"device_uplink_loss_and_latency", - "rtt":44269 - }, - "meraki":{ - "device_uplink_loss_and_latency":{ - "example": "device_uplink_loss_and_latency" - } - }, - "type":"metricsets" -} diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/docs.asciidoc deleted file mode 100644 index 47778a7c896..00000000000 --- a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the device_uplink_loss_and_latency metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/fields.yml b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/fields.yml deleted file mode 100644 index e994fdc1aee..00000000000 --- a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- name: device_uplink_loss_and_latency - type: group - release: beta - description: > - device_uplink_loss_and_latency - fields: - - name: example - type: keyword - description: > - Example field diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/device_uplink_loss_and_latency.go b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/device_uplink_loss_and_latency.go deleted file mode 100644 index 1c2fd17ec7b..00000000000 --- a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/device_uplink_loss_and_latency.go +++ /dev/null @@ -1,187 +0,0 @@ -package device_uplink_loss_and_latency - -import ( - "fmt" - "time" - - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - "github.com/elastic/elastic-agent-libs/logp" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - mb.Registry.MustAddMetricSet(meraki.ModuleName, "device_uplink_loss_and_latency", New) -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki device_uplink_loss_and_latency metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - }, nil -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - for _, org := range m.organizations { - - devices, err := meraki.GetDevices(m.client, org) - if err != nil { - return err - } - - uplinks, err := getDeviceUplinkMetrics(m.client, org, m.BaseMetricSet.Module().Config().Period) - if err != nil { - return err - } - - reportDeviceUplinkMetrics(reporter, org, devices, uplinks) - - } - - return nil -} - -func getDeviceUplinkMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { - val, res, err := client.Organizations.GetOrganizationDevicesUplinksLossAndLatency( - organizationID, - &meraki_api.GetOrganizationDevicesUplinksLossAndLatencyQueryParams{ - Timespan: period.Seconds() + 10, // slightly longer than the fetch period to ensure we don't miss measurements due to jitter - }, - ) - - if err != nil { - return nil, fmt.Errorf("GetOrganizationDevicesUplinksLossAndLatency failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - var uplinks []*Uplink - - for _, device := range *val { - uplink := &Uplink{ - DeviceSerial: meraki.Serial(device.Serial), - IP: device.IP, - Interface: device.Uplink, - } - - for _, measurement := range *device.TimeSeries { - if measurement.LossPercent != nil || measurement.LatencyMs != nil { - timestamp, err := time.Parse(time.RFC3339, measurement.Ts) - if err != nil { - return nil, fmt.Errorf("failed to parse timestamp [%s] in ResponseOrganizationsGetOrganizationDevicesUplinksLossAndLatency: %w", measurement.Ts, err) - } - - metric := UplinkMetric{Timestamp: timestamp} - if measurement.LossPercent != nil { - metric.LossPercent = measurement.LossPercent - } - if measurement.LatencyMs != nil { - metric.LatencyMs = measurement.LatencyMs - } - uplink.Metrics = append(uplink.Metrics, &metric) - } - } - - if len(uplink.Metrics) != 0 { - uplinks = append(uplinks, uplink) - } - } - - return uplinks, nil -} - -func reportDeviceUplinkMetrics(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, uplinks []*Uplink) { - metrics := []mapstr.M{} - - for _, uplink := range uplinks { - if device, ok := devices[uplink.DeviceSerial]; ok { - metric := mapstr.M{ - "uplink.ip": uplink.IP, - "upliink.interface": uplink.Interface, - // fixme: repeated code serializing device metadata to mapstr - "device.address": device.Address, - "device.firmware": device.Firmware, - "device.imei": device.Imei, - "device.lan_ip": device.LanIP, - "device.location": device.Location, - "device.mac": device.Mac, - "device.model": device.Model, - "device.name": device.Name, - "device.network_id": device.NetworkID, - "device.notes": device.Notes, - "device.product_type": device.ProductType, - "device.serial": device.Serial, - "device.tags": device.Tags, - } - - for k, v := range device.Details { - metric[fmt.Sprintf("device.details.%s", k)] = v - } - - for _, uplinkMetric := range uplink.Metrics { - metrics = append(metrics, mapstr.Union(metric, mapstr.M{ - "@timestamp": uplinkMetric.Timestamp, - "uplink.loss_percent": uplinkMetric.LossPercent, - "uplink.latency_ms": uplinkMetric.LatencyMs, - })) - } - } else { - // missing device metadata; ignore - } - } - meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) -} diff --git a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/types.go b/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/types.go deleted file mode 100644 index 364b001ce79..00000000000 --- a/x-pack/metricbeat/module/meraki/device_uplink_loss_and_latency/types.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package device_uplink_loss_and_latency - -import ( - "time" - - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" -) - -// Uplink contains static device uplink attributes; uplinks are always associated with a device -type Uplink struct { - DeviceSerial meraki.Serial - IP string - Interface string - Metrics []*UplinkMetric -} - -// UplinkMetric contains timestamped device uplink metric data points -type UplinkMetric struct { - Timestamp time.Time - LossPercent *float64 - LatencyMs *float64 -} diff --git a/x-pack/metricbeat/module/meraki/license_overview/_meta/data.json b/x-pack/metricbeat/module/meraki/license_overview/_meta/data.json deleted file mode 100644 index f9045a48a76..00000000000 --- a/x-pack/metricbeat/module/meraki/license_overview/_meta/data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" - }, - "metricset":{ - "host":"localhost", - "module":"meraki", - "name":"license_overview", - "rtt":44269 - }, - "meraki":{ - "license_overview":{ - "example": "license_overview" - } - }, - "type":"metricsets" -} diff --git a/x-pack/metricbeat/module/meraki/license_overview/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/license_overview/_meta/docs.asciidoc deleted file mode 100644 index e5f93e98a90..00000000000 --- a/x-pack/metricbeat/module/meraki/license_overview/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the license_overview metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/license_overview/_meta/fields.yml b/x-pack/metricbeat/module/meraki/license_overview/_meta/fields.yml deleted file mode 100644 index bfc580b5086..00000000000 --- a/x-pack/metricbeat/module/meraki/license_overview/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- name: license_overview - type: group - release: beta - description: > - license_overview - fields: - - name: example - type: keyword - description: > - Example field diff --git a/x-pack/metricbeat/module/meraki/license_overview/types.go b/x-pack/metricbeat/module/meraki/license_overview/types.go deleted file mode 100644 index 5f3d9e102ed..00000000000 --- a/x-pack/metricbeat/module/meraki/license_overview/types.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package license_overview - -// device unique identifier -type Serial string - -// CoterminationLicense have a common expiration date and are reported per device model -type CoterminationLicense struct { - ExpirationDate string - DeviceModel string - Status string - Count interface{} -} - -// PerDeviceLicense are reported by license state with details on expiration and activations -type PerDeviceLicense struct { - State string // one of ["Active", "Expired", "RecentlyQueued", "Expiring", "Unused", "UnusedActive", "Unassigned"] - Count *int - ExpirationState string // one of ["critial", "warning"] (only for Expiring licenses) - ExpirationThresholdDays *int // only for Expiring licenses - SoonestActivationDate string // only for Unused licenses - SoonestActivationCount *int // only for Unused licenses - OldestActivationDate string // only for UnusedActive licenses - OldestActivationCount *int // only for UnusedActive licenses - Type string // one of ["ENT", "UPGR", "ADV"] (only for Unassigned licenses) -} - -// SystemManagerLicense reports counts for seats and devices -type SystemsManagerLicense struct { - ActiveSeats *int - OrgwideEnrolledDevices *int - TotalSeats *int - UnassignedSeats *int -} diff --git a/x-pack/metricbeat/module/meraki/meraki.go b/x-pack/metricbeat/module/meraki/meraki.go deleted file mode 100644 index feea666f6d8..00000000000 --- a/x-pack/metricbeat/module/meraki/meraki.go +++ /dev/null @@ -1,164 +0,0 @@ -package meraki - -import ( - "fmt" - "net/http" - "strconv" - "time" - - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// ModuleName is the name of this module. -const ModuleName = "meraki" - -// func init() { - -// if err := mb.Registry.AddModule(ModuleName, newModule); err != nil { -// panic(err) -// } -// } - -// // Config defines all required and optional parameters for meraki metricsets -// type Config struct { -// Token string `config:"apiKey" validate:"nonzero,required"` -// Organizations []string `config:"organizations" validate:"nonzero,required"` -// } - -// // newModule adds validation that hosts is non-empty, a requirement to use the -// // mssql module. -// func newModule(base mb.BaseModule) (mb.Module, error) { -// // Validate that at least one host has been specified. -// var config Config -// if err := base.UnpackConfig(&config); err != nil { -// return nil, err -// } - -// return &base, nil -// } - -func ReportMetricsForOrganization(reporter mb.ReporterV2, organizationID string, metrics ...[]mapstr.M) { - - for _, metricSlice := range metrics { - for _, metric := range metricSlice { - event := mb.Event{ModuleFields: mapstr.M{"organization_id": organizationID}} - if ts, ok := metric["@timestamp"].(time.Time); ok { - event.Timestamp = ts - delete(metric, "@timestamp") - } - event.ModuleFields.Update(metric) - reporter.Event(event) - } - } -} - -func GetDevicesByProductType(client *meraki_api.Client, organizationID string, productTypes []string) (map[Serial]*Device, error) { - val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{ - ProductTypes: productTypes, - }) - - if err != nil { - return nil, fmt.Errorf("GetOrganizationDevices failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - devices := make(map[Serial]*Device) - for _, d := range *val { - device := Device{ - Firmware: d.Firmware, - Imei: d.Imei, - LanIP: d.LanIP, - Location: []*float64{d.Lng, d.Lat}, // (lon, lat) order is important! - Mac: d.Mac, - Model: d.Model, - Name: d.Name, - NetworkID: d.NetworkID, - Notes: d.Notes, - ProductType: d.ProductType, - Serial: d.Serial, - Tags: d.Tags, - } - if d.Details != nil { - for _, detail := range *d.Details { - device.Details[detail.Name] = detail.Value - } - } - devices[Serial(device.Serial)] = &device - } - - return devices, nil -} - -func GetDevices(client *meraki_api.Client, organizationID string) (map[Serial]*Device, error) { - val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{}) - - if err != nil { - return nil, fmt.Errorf("GetOrganizationDevices failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - devices := make(map[Serial]*Device) - for _, d := range *val { - device := Device{ - Firmware: d.Firmware, - Imei: d.Imei, - LanIP: d.LanIP, - Location: []*float64{d.Lng, d.Lat}, // (lon, lat) order is important! - Mac: d.Mac, - Model: d.Model, - Name: d.Name, - NetworkID: d.NetworkID, - Notes: d.Notes, - ProductType: d.ProductType, - Serial: d.Serial, - Tags: d.Tags, - } - if d.Details != nil { - for _, detail := range *d.Details { - device.Details[detail.Name] = detail.Value - } - } - devices[Serial(device.Serial)] = &device - } - - return devices, nil -} - -func HttpGetRequestWithMerakiRetry(url string, token string, retry int) (*http.Response, error) { - //https://developer.cisco.com/meraki/api-v1/get-device-appliance-performance/ - //url = m.meraki_url + "/api/v1/devices/" + serial + "/appliance/performance" - - // Create a Bearer string by appending string access token - var bearer = "Bearer " + token - - // Create a new request using http - req, _ := http.NewRequest("GET", url, nil) - - // add authorization header to the req - req.Header.Add("Authorization", bearer) - req.Header.Add("Accept", "application/json") - - client := &http.Client{} - response, err := client.Do(req) - - // NEED TO ADD RETRY LOGIC - //https://github.com/meraki/dashboard-api-go/blob/25b775d00e5c392677399e4fb1dfb0cfb67badce/sdk/api_client.go#L104C1-L123C3 - //https://developer.cisco.com/meraki/api-v1/rate-limit/#rate-limit - - for i := 0; i < retry && response.StatusCode == 429; i++ { - - retryHeader := response.Header.Get("Retry-After") - if _, err := strconv.Atoi(retryHeader); err == nil { - time.ParseDuration(retryHeader + "s") - // Debug - // Should we log this? - fmt.Println("Paused for time:" + retryHeader + "s") - } - response, err = client.Do(req) - - } - - return response, err - -} diff --git a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/data.json b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/data.json deleted file mode 100644 index fcc2b9f0823..00000000000 --- a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" - }, - "metricset":{ - "host":"localhost", - "module":"meraki", - "name":"network_health_channel_utilization", - "rtt":44269 - }, - "meraki":{ - "network_health_channel_utilization":{ - "example": "network_health_channel_utilization" - } - }, - "type":"metricsets" -} diff --git a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/docs.asciidoc deleted file mode 100644 index 250fccbabb6..00000000000 --- a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the network_health_channel_utilization metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/fields.yml b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/fields.yml deleted file mode 100644 index 17996f130fa..00000000000 --- a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- name: network_health_channel_utilization - type: group - release: beta - description: > - network_health_channel_utilization - fields: - - name: example - type: keyword - description: > - Example field diff --git a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/network_health_channel_utilization/network_health_channel_utilization.go deleted file mode 100644 index dc4a2c3a5b1..00000000000 --- a/x-pack/metricbeat/module/meraki/network_health_channel_utilization/network_health_channel_utilization.go +++ /dev/null @@ -1,199 +0,0 @@ -package network_health_channel_utilization - -import ( - "fmt" - "log" - "strings" - - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - "github.com/elastic/elastic-agent-libs/logp" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - mb.Registry.MustAddMetricSet(meraki.ModuleName, "network_health_channel_utilization", New) -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki network_health_channel_utilization metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - }, nil -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - for _, org := range m.organizations { - - //productTypes := []string{"wireless"} - //devices, err := meraki.GetDevicesByProductType(m.client, org, productTypes) - devices, err := meraki.GetDevices(m.client, org) - if err != nil { - return err - } - - orgNetworks, res, err := m.client.Organizations.GetOrganizationNetworks(org, &meraki_api.GetOrganizationNetworksQueryParams{}) - if err != nil { - log.Printf("Organizations.GetOrganizationNetworks failed; [%d] %s.", res.StatusCode(), res.Body()) - return fmt.Errorf("Organizations.GetOrganizationNetworks failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - //fmt.Printf("\n#DEBUG Organizations.GetOrganizationNetworks; [%d] %s.\n###################", res.StatusCode(), res.Body()) - - networkHealthUtilizations, err := getNetworkHealthChannelUtilization(m.client, orgNetworks) - if err != nil { - return err - } - - reportNetworkHealthChannelUtilization(reporter, org, devices, networkHealthUtilizations) - - } - - return nil -} - -func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) ([]*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization, error) { - - var networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization - - for _, network := range *networks { - - for _, product_type := range network.ProductTypes { - - if strings.Compare(product_type, "wireless") == 0 { - - networkHealthUtilization, _, err := client.Networks.GetNetworkNetworkHealthChannelUtilization(network.ID, &meraki_api.GetNetworkNetworkHealthChannelUtilizationQueryParams{}) - if err != nil { - //return nil, fmt.Errorf("Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - //log.Printf("\n#ERROR Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s.", res.StatusCode(), res.Body()) - //fmt.Printf("\n#ERROR Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s.\n###################", res.StatusCode(), res.Body()) - } else { - //fmt.Printf("\n#DEBUG Networks.GetNetworkNetworkHealthChannelUtilization; [%d] %s. \n###################", res.StatusCode(), res.Body()) - networkHealthUtilizations = append(networkHealthUtilizations, networkHealthUtilization) - } - - } - } - } - - return networkHealthUtilizations, nil -} - -func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization) { - - metrics := []mapstr.M{} - //Note: API does not specifiy if wireless devices only, so iterating through all network devices. API is to ambiguous - //for _, device := range devices { - - //fmt.Printf("\n#DEBUG device Info: \nSerial: %s \nName: %s \nMode: %s \nNetworkID: %s \nProducttype: %s", device.Serial, device.Name, device.Model, device.NetworkID, device.ProductType) - - //fmt.Printf("\n#DEBUG reportNetworkHealthCHannelUtilization") - - for _, networkHealthUtil := range networkHealthUtilizations { - //fmt.Printf("\n#DEBUG reportNetworkHealthCHannelUtilization For LOOP 1") - - for _, network := range *networkHealthUtil { - - //fmt.Printf("\n#DEBUG reportNetworkHealthCHannelUtilization For LOOP 2") - - metric := mapstr.M{ - "network.health.channel.radio.serial": network.Serial, - "network.health.channel.radio.model": network.Model, - "network.health.channel.radio.tags": network.Tags, - } - - for _, wifi0 := range *network.Wifi0 { - metric["network.health.channel.radio.wifi0.start_time"] = wifi0.StartTime - metric["network.health.channel.radio.wifi0.end_time"] = wifi0.EndTime - metric["network.health.channel.radio.wifi0.utilization80211"] = wifi0.Utilization80211 - metric["network.health.channel.radio.wifi0.utilizationNon80211"] = wifi0.UtilizationNon80211 - metric["network.health.channel.radio.wifi0.utilizationTotal"] = wifi0.UtilizationTotal - } - - for _, wifi1 := range *network.Wifi1 { - metric["network.health.channel.radio.wifi1.start_time"] = wifi1.StartTime - metric["network.health.channel.radio.wifi1.end_time"] = wifi1.EndTime - metric["network.health.channel.radio.wifi1.utilization80211"] = wifi1.Utilization80211 - metric["network.health.channel.radio.wifi1.utilizationNon80211"] = wifi1.UtilizationNon80211 - metric["network.health.channel.radio.wifi1.utilizationTotal"] = wifi1.UtilizationTotal - } - - if device, ok := devices[meraki.Serial(network.Serial)]; ok { - metric["device.address"] = device.Address - metric["device.firmware"] = device.Firmware - metric["device.imei"] = device.Imei - metric["device.lan_ip"] = device.LanIP - metric["device.location"] = device.Location - metric["device.mac"] = device.Mac - metric["device.model"] = device.Model - metric["device.name"] = device.Name - metric["device.network_id"] = device.NetworkID - metric["device.notes"] = device.Notes - metric["device.product_type"] = device.ProductType - metric["device.serial"] = device.Serial - metric["device.tags"] = device.Tags - - } - metrics = append(metrics, metric) - } - // for k, v := range device.Details { - // metric[fmt.Sprintf("device.details.%s", k)] = v - // } - } - meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) -} diff --git a/x-pack/metricbeat/module/meraki/types.go b/x-pack/metricbeat/module/meraki/types.go deleted file mode 100644 index 9b55ea2d86c..00000000000 --- a/x-pack/metricbeat/module/meraki/types.go +++ /dev/null @@ -1,22 +0,0 @@ -package meraki - -// device unique identifier -type Serial string - -// Device contains static device attributes (i.e. dimensions) -type Device struct { - Address string - Details map[string]string - Firmware string - Imei *float64 - LanIP string - Location []*float64 - Mac string - Model string - Name string - NetworkID string - Notes string - ProductType string // one of ["appliance", "camera", "cellularGateway", "secureConnect", "sensor", "switch", "systemsManager", "wireless", "wirelessController"] - Serial string - Tags []string -} diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/data.json b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/data.json deleted file mode 100644 index d50fc308510..00000000000 --- a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" - }, - "metricset":{ - "host":"localhost", - "module":"meraki", - "name":"wireless_device_channel_utilization", - "rtt":44269 - }, - "meraki":{ - "wireless_device_channel_utilization":{ - "example": "wireless_device_channel_utilization" - } - }, - "type":"metricsets" -} diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/docs.asciidoc b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/docs.asciidoc deleted file mode 100644 index 390769cfd3f..00000000000 --- a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/docs.asciidoc +++ /dev/null @@ -1 +0,0 @@ -This is the wireless_device_channel_utilization metricset of the module meraki. diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/fields.yml b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/fields.yml deleted file mode 100644 index d68a1e7b036..00000000000 --- a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/_meta/fields.yml +++ /dev/null @@ -1,10 +0,0 @@ -- name: wireless_device_channel_utilization - type: group - release: beta - description: > - wireless_device_channel_utilization - fields: - - name: example - type: keyword - description: > - Example field diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/types.go b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/types.go deleted file mode 100644 index c7bbbf8723f..00000000000 --- a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/types.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package wireless_device_channel_utilization - -import ( - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" -) - -// Uplink contains static device uplink attributes; uplinks are always associated with a device -type WirelessDevicesChannelUtilizationByDevice []struct { - Serial meraki.Serial `json:"serial"` - Mac string `json:"mac"` - Network struct { - ID string `json:"id"` - } `json:"network"` - ByBand []struct { - Band string `json:"band"` - Wifi struct { - Percentage float64 `json:"percentage"` - } `json:"wifi"` - NonWifi struct { - Percentage float64 `json:"percentage"` - } `json:"nonWifi"` - Total struct { - Percentage float64 `json:"percentage"` - } `json:"total"` - } `json:"byBand"` -} diff --git a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/wireless_device_channel_utilization.go b/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/wireless_device_channel_utilization.go deleted file mode 100644 index a7e9c123dd7..00000000000 --- a/x-pack/metricbeat/module/meraki/wireless_device_channel_utilization/wireless_device_channel_utilization.go +++ /dev/null @@ -1,149 +0,0 @@ -package wireless_device_channel_utilization - -import ( - "encoding/json" - "fmt" - - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" - "github.com/elastic/elastic-agent-libs/logp" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" -) - -// init registers the MetricSet with the central registry as soon as the program -// starts. The New function will be called later to instantiate an instance of -// the MetricSet for each host is defined in the module's configuration. After the -// MetricSet has been created then Fetch will begin to be called periodically. -func init() { - mb.Registry.MustAddMetricSet(meraki.ModuleName, "wireless_device_channel_utilization", New) -} - -type config struct { - BaseURL string `config:"apiBaseURL"` - ApiKey string `config:"apiKey"` - DebugMode string `config:"apiDebugMode"` - Organizations []string `config:"organizations"` - // todo: device filtering? -} - -func defaultConfig() *config { - return &config{ - BaseURL: "https://api.meraki.com", - DebugMode: "false", - } -} - -// MetricSet holds any configuration or state information. It must implement -// the mb.MetricSet interface. And this is best achieved by embedding -// mb.BaseMetricSet because it implements all of the required mb.MetricSet -// interface methods except for Fetch. -type MetricSet struct { - mb.BaseMetricSet - logger *logp.Logger - client *meraki_api.Client - organizations []string -} - -// New creates a new instance of the MetricSet. New is responsible for unpacking -// any MetricSet specific configuration options if there are any. -func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The meraki wireless_device_channel_utilization metricset is beta.") - - logger := logp.NewLogger(base.FullyQualifiedName()) - - config := defaultConfig() - if err := base.Module().UnpackConfig(config); err != nil { - return nil, err - } - - logger.Debugf("loaded config: %v", config) - client, err := meraki_api.NewClientWithOptions(config.BaseURL, config.ApiKey, config.DebugMode, "Metricbeat Elastic") - if err != nil { - logger.Error("creating Meraki dashboard API client failed: %w", err) - return nil, err - } - - return &MetricSet{ - BaseMetricSet: base, - logger: logger, - client: client, - organizations: config.Organizations, - }, nil -} - -// Fetch method implements the data gathering and data conversion to the right -// format. It publishes the event which is then forwarded to the output. In case -// of an error set the Error field of mb.Event or simply call report.Error(). -func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { - for _, org := range m.organizations { - - devices, err := meraki.GetDevices(m.client, org) - if err != nil { - return err - } - - res, err := m.client.Devices.GetOrganizationWirelessDevicesChannelUtilizationByDevice(org, &meraki_api.GetOrganizationWirelessDevicesChannelUtilizationByDeviceQueryParams{}) - if err != nil { - return fmt.Errorf("GetOrganizationWirelessDevicesChannelUtilizationByDevice failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - //debug - //fmt.Printf("\n #DEBUG Devices.GetOrganizationWirelessDevicesChannelUtilizationByDevice; [%d] %s.", res.StatusCode(), res.Body()) - - var wirelessDevices WirelessDevicesChannelUtilizationByDevice - err = json.Unmarshal(res.Body(), &wirelessDevices) - if err != nil { - return fmt.Errorf("device_network_health_channel_utilization json umarshal failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - reportWirelessDeviceChannelUtilization(reporter, org, devices, wirelessDevices) - - } - - return nil -} - -func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[meraki.Serial]*meraki.Device, wirelessDevices WirelessDevicesChannelUtilizationByDevice) { - - metrics := []mapstr.M{} - - for _, wirelessDevice := range wirelessDevices { - - if device, ok := devices[meraki.Serial(wirelessDevice.Serial)]; ok { - - metric := mapstr.M{ - "wireless.device.address": device.Address, - "wireless.device.firmware": device.Firmware, - "wireless.device.imei": device.Imei, - "wireless.device.lan_ip": device.LanIP, - "wireless.device.location": device.Location, - "wireless.device.mac": device.Mac, - "wireless.device.model": device.Model, - "wireless.device.name": device.Name, - "wireless.device.network_id": device.NetworkID, - "wireless.device.notes": device.Notes, - "wireless.device.product_type": device.ProductType, - "wireless.device.serial": device.Serial, - "wireless.device.tags": device.Tags, - } - - for _, v := range wirelessDevice.ByBand { - metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.wifi.percentage", v.Band)] = v.Wifi.Percentage - metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.nonwifi.percentage", v.Band)] = v.NonWifi.Percentage - metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.total.percentage", v.Band)] = v.Total.Percentage - } - - // for k, v := range device.Details { - // metric[fmt.Sprintf("wireless.device.details.%s", k)] = v - // } - - metrics = append(metrics, metric) - - } - - } - meraki.ReportMetricsForOrganization(reporter, organizationID, metrics) -} diff --git a/x-pack/metricbeat/modules.d/meraki.yml.disabled b/x-pack/metricbeat/modules.d/meraki.yml.disabled index a7cdac9e3a3..f0770e68a3f 100644 --- a/x-pack/metricbeat/modules.d/meraki.yml.disabled +++ b/x-pack/metricbeat/modules.d/meraki.yml.disabled @@ -2,17 +2,7 @@ # Docs: https://www.elastic.co/guide/en/beats/metricbeat/main/metricbeat-module-meraki.html - module: meraki - metricsets: [ - "device_status", - "appliance_uplink_overview", - "appliance_uplink_status_and_ha", - "cellular_gateway_uplink_status", - "device_appliance_performance_score", - "device_uplink_loss_and_latency", - "license_overview", - "network_health_channel_utilization", - "wireless_device_channel_utilization" - ] + metricsets: ["device_health"] enabled: true period: 60s hosts: ["https://api.meraki.com"] From 53ae668ab2f61c15bacfc4aab5f0ed5f4ff6b216 Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Sat, 7 Sep 2024 13:29:35 -0600 Subject: [PATCH 03/13] added tunnel support aka VPN support by Device --- .../device_appliance_performance_score.go | 34 ++++---- ... device_appliance_uplink_status_and_ha.go} | 26 +++--- ... device_cellular_gateway_uplink_status.go} | 26 +++--- .../meraki/device_health/device_health.go | 16 ++++ .../meraki/device_health/device_license.go | 53 ++++++++++++ ...device_network_appliance_vpn_sitetosite.go | 83 +++++++++++++++++++ ...ice_network_health_channel_utilization.go} | 24 +++--- ...ce_wireless_device_channel_utilization.go} | 26 +++--- .../module/meraki/device_health/types.go | 2 + x-pack/metricbeat/module/meraki/fields.go | 23 +++++ 10 files changed, 243 insertions(+), 70 deletions(-) rename x-pack/metricbeat/module/meraki/device_health/{appliance_uplink_status_and_ha.go => device_appliance_uplink_status_and_ha.go} (63%) rename x-pack/metricbeat/module/meraki/device_health/{cellular_gateway_uplink_status.go => device_cellular_gateway_uplink_status.go} (73%) create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_license.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go rename x-pack/metricbeat/module/meraki/device_health/{network_health_channel_utilization.go => device_network_health_channel_utilization.go} (70%) rename x-pack/metricbeat/module/meraki/device_health/{wireless_device_channel_utilization.go => device_wireless_device_channel_utilization.go} (60%) create mode 100644 x-pack/metricbeat/module/meraki/fields.go diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go index 71702c5b886..5dc8384db7b 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go @@ -79,30 +79,26 @@ func reportPerformanceScoreMetrics(reporter mb.ReporterV2, organizationID string devicePerformanceScoreMetrics := []mapstr.M{} for serial, device := range devices { metric := mapstr.M{ - "device.appliance.address": device.Address, - "device.appliance.firmware": device.Firmware, - "device.appliance.imei": device.Imei, - "device.appliance.lan_ip": device.LanIP, - "device.appliance.location": device.Location, - "device.appliance.mac": device.Mac, - "device.appliance.model": device.Model, - "device.appliance.name": device.Name, - "device.appliance.network_id": device.NetworkID, - "device.appliance.notes": device.Notes, - "device.appliance.product_type": device.ProductType, - "device.appliance.serial": device.Serial, - "device.appliance.tags": device.Tags, - } - - for k, v := range device.Details { - metric[fmt.Sprintf("device.appliance.details.%s", k)] = v + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, } if score, ok := devicePerformanceScores[serial]; ok { if score.HttpStatusCode == 204 { - metric["device.appliance.performance.http_status_code"] = score.HttpStatusCode + metric["device.performance.http_status_code"] = score.HttpStatusCode } else { - metric["device.appliance.performance.score"] = score.PerformanceScore + metric["device.performance.score"] = score.PerformanceScore } } diff --git a/x-pack/metricbeat/module/meraki/device_health/appliance_uplink_status_and_ha.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go similarity index 63% rename from x-pack/metricbeat/module/meraki/device_health/appliance_uplink_status_and_ha.go rename to x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go index 3fde8e65748..32b5c4e8885 100644 --- a/x-pack/metricbeat/module/meraki/device_health/appliance_uplink_status_and_ha.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go @@ -18,19 +18,19 @@ func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string "appliance.uplink.high_availablity.enabled": uplink.HighAvailability.Enabled, "appliance.uplink.high_availablity.role": uplink.HighAvailability.Role, "appliance.uplink.last_reported_at": uplink.LastReportedAt, - "appliance.address": device.Address, - "appliance.firmware": device.Firmware, - "appliance.imei": device.Imei, - "appliance.lan_ip": device.LanIP, - "appliance.location": device.Location, - "appliance.mac": device.Mac, - "appliance.model": device.Model, - "appliance.name": device.Name, - "appliance.network_id": device.NetworkID, - "appliance.notes": device.Notes, - "appliance.product_type": device.ProductType, - "appliance.serial": device.Serial, - "appliance.tags": device.Tags, + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, } for _, item := range *uplink.Uplinks { diff --git a/x-pack/metricbeat/module/meraki/device_health/cellular_gateway_uplink_status.go b/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go similarity index 73% rename from x-pack/metricbeat/module/meraki/device_health/cellular_gateway_uplink_status.go rename to x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go index 653597a5ace..7d19c2baa11 100644 --- a/x-pack/metricbeat/module/meraki/device_health/cellular_gateway_uplink_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go @@ -19,19 +19,19 @@ func reportCellularGatewayApplianceUplinkStatuses(reporter mb.ReporterV2, organi metric := mapstr.M{ "cellular.gateway.uplink.network_id": uplink.NetworkID, "cellular.gateway.uplink.last_reported_at": uplink.LastReportedAt, - "cellular.gateway.address": device.Address, - "cellular.gateway.firmware": device.Firmware, - "cellular.gateway.imei": device.Imei, - "cellular.gateway.lan_ip": device.LanIP, - "cellular.gateway.location": device.Location, - "cellular.gateway.mac": device.Mac, - "cellular.gateway.model": device.Model, - "cellular.gateway.name": device.Name, - "cellular.gateway.network_id": device.NetworkID, - "cellular.gateway.notes": device.Notes, - "cellular.gateway.product_type": device.ProductType, - "cellular.gateway.serial": device.Serial, - "cellular.gateway.tags": device.Tags, + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, } for i, item := range *uplink.Uplinks { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go index 697c0ee6677..a7df69ad476 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_health.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -168,6 +168,22 @@ func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { } reportWirelessDeviceChannelUtilization(reporter, org, devices, wirelessDevices) + //Use Org Networks Retrieved above + //Get VPN site_to_site + //Report VPN site_to_site + networkVPNSiteToSites, err := getNetworkApplianceVPNSiteToSite(m.client, orgNetworks) + if err != nil { + return err + } + reportNetwrokApplianceVPNSiteToSite(reporter, org, devices, networkVPNSiteToSites) + + //Get & Report Organization License by Device + license_val, license_res, license_err := m.client.Organizations.GetOrganizationLicenses(org, &meraki_api.GetOrganizationLicensesQueryParams{}) + if license_err != nil { + return fmt.Errorf("Organizations.GetOrganizationLicensesfailed; [%d] %s. %w", license_res.StatusCode(), license_res.Body(), license_err) + } + reportOrganizationDeviceLicenses(reporter, org, devices, license_val) + } return nil diff --git a/x-pack/metricbeat/module/meraki/device_health/device_license.go b/x-pack/metricbeat/module/meraki/device_health/device_license.go new file mode 100644 index 00000000000..bdb398cd938 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_license.go @@ -0,0 +1,53 @@ +package device_health + +import ( + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportOrganizationDeviceLicenses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, orgLicenseBySerial *meraki_api.ResponseOrganizationsGetOrganizationLicenses) { + + metrics := []mapstr.M{} + + for _, license := range *orgLicenseBySerial { + + if device, ok := devices[Serial(license.DeviceSerial)]; ok { + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + "device.license.activation_date": license.ActivationDate, + "device.license.claim_date": license.ClaimDate, + "device.license.device_serial": license.DeviceSerial, + "device.license.duration_in_days": license.DurationInDays, + "device.license.expiration_date": license.ExpirationDate, + "device.license.head_license_id": license.HeadLicenseID, + "device.license.id": license.ID, + "device.license.license_type": license.LicenseType, + "device.license.network_id": license.NetworkID, + "device.license.order_number": license.OrderNumber, + "device.license.seat_count": license.SeatCount, + "device.license.state": license.State, + "device.license.total_duration_in_days": license.TotalDurationInDays, + //"device.license.license_key": license.LicenseKey, Not sure we want private key information on metric data in elastic + //"device.license.11": license.PermanentlyQueuedLicenses, //DEPRECATED List of permanently queued licenses attached to the license. Instead, use /organizations/{organizationId}/licenses?deviceSerial= to retrieved queued licenses for a given device. + } + + metrics = append(metrics, metric) + } + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go new file mode 100644 index 00000000000..c5e68f873bb --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go @@ -0,0 +1,83 @@ +package device_health + +import ( + "fmt" + "strings" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getNetworkApplianceVPNSiteToSite(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) (map[NetworkID]*meraki_api.ResponseApplianceGetNetworkApplianceVpnSiteToSiteVpn, error) { + + networkVPNSiteToSites := make(map[NetworkID]*meraki_api.ResponseApplianceGetNetworkApplianceVpnSiteToSiteVpn) + + for _, network := range *networks { + + networkVPNSiteToSite, res, err := client.Appliance.GetNetworkApplianceVpnSiteToSiteVpn(network.ID) + if err != nil { + //Error: "This endpoint only supports MX networks" + // We just swallow this error but do not append to the list + if !(strings.Contains(string(res.Body()), "MX network")) { + //Any other problem we are going to return an error + return nil, fmt.Errorf("Appliance.GetNetworkApplianceVpnSiteToSiteVpn failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + } else { + networkVPNSiteToSites[NetworkID(network.ID)] = networkVPNSiteToSite + + } + + } + + return networkVPNSiteToSites, nil +} + +func reportNetwrokApplianceVPNSiteToSite(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, networkVPNSiteToSites map[NetworkID]*meraki_api.ResponseApplianceGetNetworkApplianceVpnSiteToSiteVpn) { + metrics := []mapstr.M{} + + for network_id, networkVPNSiteToSite := range networkVPNSiteToSites { + for _, device := range devices { + if device.NetworkID == string(network_id) { + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + metric["network.appliance.vpn.site_to_site.network_id"] = network_id + metric["network.appliance.vpn.site_to_site.mode"] = networkVPNSiteToSite.Mode + + if networkVPNSiteToSite.Mode != "none" { + if networkVPNSiteToSite.Hubs != nil { + for k, hub := range *networkVPNSiteToSite.Hubs { + metric[fmt.Sprintf("network.appliance.vpn.site_to_site.hub.%d.hub_id", k)] = hub.HubID + metric[fmt.Sprintf("network.appliance.vpn.site_to_site.hub.%d.use_default_route", k)] = *hub.UseDefaultRoute + } + } + + if networkVPNSiteToSite.Subnets != nil { + for k, subnet := range *networkVPNSiteToSite.Subnets { + metric[fmt.Sprintf("network.appliance.vpn.site_to_site.subnet.%d.local_subnet", k)] = subnet.LocalSubnet + metric[fmt.Sprintf("network.appliance.vpn.site_to_site.subnet.%d.use_vpn", k)] = *subnet.UseVpn + } + } + } + metrics = append(metrics, metric) + } + } + + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go similarity index 70% rename from x-pack/metricbeat/module/meraki/device_health/network_health_channel_utilization.go rename to x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go index 9998edaacdc..bf364afc059 100644 --- a/x-pack/metricbeat/module/meraki/device_health/network_health_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go @@ -49,20 +49,20 @@ func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationI "network.health.channel.radio.tags": network.Tags, } - for _, wifi0 := range *network.Wifi0 { - metric["network.health.channel.radio.wifi0.start_time"] = wifi0.StartTime - metric["network.health.channel.radio.wifi0.end_time"] = wifi0.EndTime - metric["network.health.channel.radio.wifi0.utilization80211"] = wifi0.Utilization80211 - metric["network.health.channel.radio.wifi0.utilizationNon80211"] = wifi0.UtilizationNon80211 - metric["network.health.channel.radio.wifi0.utilizationTotal"] = wifi0.UtilizationTotal + for k, wifi0 := range *network.Wifi0 { + metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.start_time", k)] = wifi0.StartTime + metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.end_time", k)] = wifi0.EndTime + metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.utilization80211", k)] = wifi0.Utilization80211 + metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.utilizationNon80211", k)] = wifi0.UtilizationNon80211 + metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.utilizationTotal", k)] = wifi0.UtilizationTotal } - for _, wifi1 := range *network.Wifi1 { - metric["network.health.channel.radio.wifi1.start_time"] = wifi1.StartTime - metric["network.health.channel.radio.wifi1.end_time"] = wifi1.EndTime - metric["network.health.channel.radio.wifi1.utilization80211"] = wifi1.Utilization80211 - metric["network.health.channel.radio.wifi1.utilizationNon80211"] = wifi1.UtilizationNon80211 - metric["network.health.channel.radio.wifi1.utilizationTotal"] = wifi1.UtilizationTotal + for k, wifi1 := range *network.Wifi1 { + metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.start_time", k)] = wifi1.StartTime + metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.end_time", k)] = wifi1.EndTime + metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.utilization80211", k)] = wifi1.Utilization80211 + metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.utilizationNon80211", k)] = wifi1.UtilizationNon80211 + metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.utilizationTotal", k)] = wifi1.UtilizationTotal } if device, ok := devices[Serial(network.Serial)]; ok { diff --git a/x-pack/metricbeat/module/meraki/device_health/wireless_device_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go similarity index 60% rename from x-pack/metricbeat/module/meraki/device_health/wireless_device_channel_utilization.go rename to x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go index 4b6f8e6bc63..a2ee020b285 100644 --- a/x-pack/metricbeat/module/meraki/device_health/wireless_device_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go @@ -16,19 +16,19 @@ func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organization if device, ok := devices[Serial(wirelessDevice.Serial)]; ok { metric := mapstr.M{ - "wireless.device.address": device.Address, - "wireless.device.firmware": device.Firmware, - "wireless.device.imei": device.Imei, - "wireless.device.lan_ip": device.LanIP, - "wireless.device.location": device.Location, - "wireless.device.mac": device.Mac, - "wireless.device.model": device.Model, - "wireless.device.name": device.Name, - "wireless.device.network_id": device.NetworkID, - "wireless.device.notes": device.Notes, - "wireless.device.product_type": device.ProductType, - "wireless.device.serial": device.Serial, - "wireless.device.tags": device.Tags, + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, } for _, v := range wirelessDevice.ByBand { diff --git a/x-pack/metricbeat/module/meraki/device_health/types.go b/x-pack/metricbeat/module/meraki/device_health/types.go index 32a4c1530b5..fd9978e0f36 100644 --- a/x-pack/metricbeat/module/meraki/device_health/types.go +++ b/x-pack/metricbeat/module/meraki/device_health/types.go @@ -11,6 +11,8 @@ import ( // device unique identifier type Serial string +type NetworkID string + // Device contains static device attributes (i.e. dimensions) type Device struct { Address string diff --git a/x-pack/metricbeat/module/meraki/fields.go b/x-pack/metricbeat/module/meraki/fields.go new file mode 100644 index 00000000000..37bfb38f6db --- /dev/null +++ b/x-pack/metricbeat/module/meraki/fields.go @@ -0,0 +1,23 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// Code generated by beats/dev-tools/cmd/asset/asset.go - DO NOT EDIT. + +package meraki + +import ( + "github.com/elastic/beats/v7/libbeat/asset" +) + +func init() { + if err := asset.SetFields("metricbeat", "meraki", asset.ModuleFieldsPri, AssetMeraki); err != nil { + panic(err) + } +} + +// AssetMeraki returns asset data. +// This is the base64 encoded zlib format compressed contents of module/meraki. +func AssetMeraki() string { + return "eJx8j0GugyAYhPecYuLeC7B4u3eOhsq0EkEIYltv3yjaUEP6LZnwzfwtBi4SjlENRgDJJEuJJj80Aoi0VBMlrkxKAJpTF01Ixo8SfwLA/hvO69lSADdDqye5ZS1G5Vg0rKQlUOIe/Rz2l4r121O6NB+m46Wnsqn/pDXtyvmAg2rlEdULzoPKUXwpF7bzS/KkgcvTR33KfgxY+c/CXCreAQAA///oWnJl" +} From c122fc8e9feee1355ebb4d251dbd48e8ae6b2b55 Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Sun, 8 Sep 2024 00:41:47 -0600 Subject: [PATCH 04/13] adding interfaces aka switch ports and switch port status --- .../meraki/device_health/device_health.go | 27 +++- .../device_network_appliance_ports.go | 77 ++++++++++++ ...device_network_appliance_vpn_sitetosite.go | 2 +- ...vice_network_health_channel_utilization.go | 2 +- .../device_health/device_switch_port.go | 64 ++++++++++ .../device_switch_port_status.go | 117 ++++++++++++++++++ .../module/meraki/device_health/types.go | 1 + 7 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_switch_port.go create mode 100644 x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go index a7df69ad476..80dde438ad0 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_health.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -180,10 +180,35 @@ func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { //Get & Report Organization License by Device license_val, license_res, license_err := m.client.Organizations.GetOrganizationLicenses(org, &meraki_api.GetOrganizationLicensesQueryParams{}) if license_err != nil { - return fmt.Errorf("Organizations.GetOrganizationLicensesfailed; [%d] %s. %w", license_res.StatusCode(), license_res.Body(), license_err) + return fmt.Errorf("Organizations.GetOrganizationLicenses failed; [%d] %s. %w", license_res.StatusCode(), license_res.Body(), license_err) } reportOrganizationDeviceLicenses(reporter, org, devices, license_val) + //Use Org Networks Retrieved above + //Get Network Ports + //Report Network Ports By Device + networkPorts, err := getNetworkAppliancePorts(m.client, orgNetworks) + if err != nil { + return err + } + reportNetwrokAppliancePorts(reporter, org, devices, networkPorts) + + //Get & Report Organization License by Device + switchPorts_val, switchPorts_res, switchPorts_err := m.client.Switch.GetOrganizationSwitchPortsBySwitch(org, &meraki_api.GetOrganizationSwitchPortsBySwitchQueryParams{}) + if switchPorts_err != nil { + return fmt.Errorf("Switch.GetOrganizationSwitchPortsBySwitch failed; [%d] %s. %w", switchPorts_res.StatusCode(), switchPorts_res.Body(), switchPorts_err) + } + reportOrganizationDeviceSwitchPortBySwitch(reporter, org, devices, switchPorts_val) + + //Use Org Networks Retrieved above + //Get Network Ports + //Report Network Ports By Device + switchPortStatusBySerials, err := getSwitchPortStatusBySerial(m.client, org) + if err != nil { + return err + } + reportSwitchPortStatusBySerial(reporter, org, devices, switchPortStatusBySerials) + } return nil diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go new file mode 100644 index 00000000000..9a767a9fcf4 --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go @@ -0,0 +1,77 @@ +package device_health + +import ( + "fmt" + "strings" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getNetworkAppliancePorts(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) (map[NetworkID]*meraki_api.ResponseApplianceGetNetworkAppliancePorts, error) { + + networkPorts := make(map[NetworkID]*meraki_api.ResponseApplianceGetNetworkAppliancePorts) + + for _, network := range *networks { + + networkPort, res, err := client.Appliance.GetNetworkAppliancePorts(network.ID) + if err != nil { + //Error: "This endpoint only supports MX networks" or "VLANs are not enabled for this network" + // We just ignore theses error but do not append to the list + if !(strings.Contains(string(res.Body()), "VLANs are not enabled")) && !(strings.Contains(string(res.Body()), "MX networks")) { + //Any other problem we are going to return an error + return nil, fmt.Errorf("Appliance.GetNetworkAppliancePorts failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + } else { + networkPorts[NetworkID(network.ID)] = networkPort + + } + + } + + return networkPorts, nil +} + +func reportNetwrokAppliancePorts(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, networkPorts map[NetworkID]*meraki_api.ResponseApplianceGetNetworkAppliancePorts) { + metrics := []mapstr.M{} + + for network_id, networkPort := range networkPorts { + for _, device := range devices { + if device.NetworkID == string(network_id) { + metric := mapstr.M{ + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for i, port := range *networkPort { + metric[fmt.Sprintf("appliance.network.port.item_%d.number", i)] = port.Number + metric[fmt.Sprintf("appliance.network.port.item_%d.enabled", i)] = port.Enabled + metric[fmt.Sprintf("appliance.network.port.item_%d.type", i)] = port.Type + metric[fmt.Sprintf("appliance.network.port.item_%d.drop_untagged_traffic", i)] = port.DropUntaggedTraffic + metric[fmt.Sprintf("appliance.network.port.item_%d.vlan", i)] = port.VLAN + metric[fmt.Sprintf("appliance.network.port.item_%d.allowed_vlans", i)] = port.AllowedVLANs + metric[fmt.Sprintf("appliance.network.port.item_%d.access_policy", i)] = port.AccessPolicy + } + + metrics = append(metrics, metric) + + } + } + + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go index c5e68f873bb..325f43d4ff8 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go @@ -18,7 +18,7 @@ func getNetworkApplianceVPNSiteToSite(client *meraki_api.Client, networks *merak networkVPNSiteToSite, res, err := client.Appliance.GetNetworkApplianceVpnSiteToSiteVpn(network.ID) if err != nil { //Error: "This endpoint only supports MX networks" - // We just swallow this error but do not append to the list + // We just ignore this error but do not append to the list if !(strings.Contains(string(res.Body()), "MX network")) { //Any other problem we are going to return an error return nil, fmt.Errorf("Appliance.GetNetworkApplianceVpnSiteToSiteVpn failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go index bf364afc059..44b371ba8d1 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go @@ -22,7 +22,7 @@ func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *mer networkHealthUtilization, res, err := client.Networks.GetNetworkNetworkHealthChannelUtilization(network.ID, &meraki_api.GetNetworkNetworkHealthChannelUtilizationQueryParams{}) if err != nil { //"This endpoint is only available for networks on MR 27.0 or above." - // We just swallow this error but do not append to the list + // We just ignore this error but do not append to the list if !(strings.Contains(string(res.Body()), "MR 27.0")) { //Any other problem we are going to return an error return nil, fmt.Errorf("Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go b/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go new file mode 100644 index 00000000000..2928e14474c --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go @@ -0,0 +1,64 @@ +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func reportOrganizationDeviceSwitchPortBySwitch(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, orgSwitchPortsBySwitch *meraki_api.ResponseSwitchGetOrganizationSwitchPortsBySwitch) { + + metrics := []mapstr.M{} + + for _, switchPort := range *orgSwitchPortsBySwitch { + + metric := mapstr.M{ + "switch.port.name": switchPort.Name, + "switch.port.serial": switchPort.Serial, + "switch.port.mac": switchPort.Mac, + "switch.port.network.name": switchPort.Network.Name, + "switch.port.network.id": switchPort.Network.ID, + "switch.port.model": switchPort.Model, + } + + for i, port := range *switchPort.Ports { + metric[fmt.Sprintf("switch.port.item_%d.port_id", i)] = port.PortID + metric[fmt.Sprintf("switch.port.item_%d.name", i)] = port.Name + metric[fmt.Sprintf("switch.port.item_%d.tags", i)] = port.Tags + metric[fmt.Sprintf("switch.port.item_%d.enabled", i)] = port.Enabled + metric[fmt.Sprintf("switch.port.item_%d.poe_enabled", i)] = port.PoeEnabled + metric[fmt.Sprintf("switch.port.item_%d.vlan", i)] = port.VLAN + metric[fmt.Sprintf("switch.port.item_%d.voice_vlan", i)] = port.VoiceVLAN + metric[fmt.Sprintf("switch.port.item_%d.allowed_vlans", i)] = port.AllowedVLANs + metric[fmt.Sprintf("switch.port.item_%d.rstp_enabled", i)] = port.RstpEnabled + metric[fmt.Sprintf("switch.port.item_%d.stp_guard", i)] = port.StpGuard + metric[fmt.Sprintf("switch.port.item_%d.link_negotiation", i)] = port.LinkNegotiation + metric[fmt.Sprintf("switch.port.item_%d.access_policy_type", i)] = port.AccessPolicyType + metric[fmt.Sprintf("switch.port.item_%d.sticky_mac_allow_list", i)] = port.StickyMacAllowList + metric[fmt.Sprintf("switch.port.item_%d.sticky_mac_allow_list_limit", i)] = port.StickyMacAllowListLimit + } + + if device, ok := devices[Serial(switchPort.Serial)]; ok { + metric["device.address"] = device.Address + metric["device.firmware"] = device.Firmware + metric["device.imei"] = device.Imei + metric["device.lan_ip"] = device.LanIP + metric["device.location"] = device.Location + metric["device.mac"] = device.Mac + metric["device.model"] = device.Model + metric["device.name"] = device.Name + metric["device.network_id"] = device.NetworkID + metric["device.notes"] = device.Notes + metric["device.product_type"] = device.ProductType + metric["device.serial"] = device.Serial + metric["device.tags"] = device.Tags + + } + metrics = append(metrics, metric) + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go b/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go new file mode 100644 index 00000000000..82f7e49100f --- /dev/null +++ b/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go @@ -0,0 +1,117 @@ +package device_health + +import ( + "fmt" + + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" +) + +func getSwitchPortStatusBySerial(client *meraki_api.Client, org string) (map[Serial]*meraki_api.ResponseSwitchGetDeviceSwitchPortsStatuses, error) { + + switchSerials_val, switchSerials_res, switchSerials_err := client.Switch.GetOrganizationSwitchPortsBySwitch(org, &meraki_api.GetOrganizationSwitchPortsBySwitchQueryParams{}) + if switchSerials_err != nil { + return nil, fmt.Errorf("Switch.GetOrganizationSwitchPortsBySwitch failed; [%d] %s. %w", switchSerials_res.StatusCode(), switchSerials_res.Body(), switchSerials_err) + } + + var switchSerials []string + for _, switchSerial := range *switchSerials_val { + switchSerials = append(switchSerials, switchSerial.Serial) + } + + serialSwitchPortStatuses := make(map[Serial]*meraki_api.ResponseSwitchGetDeviceSwitchPortsStatuses) + for _, serial := range switchSerials { + switchPortStatuses_val, switchPortStatuses_res, switchPortStatuses_err := client.Switch.GetDeviceSwitchPortsStatuses(serial, &meraki_api.GetDeviceSwitchPortsStatusesQueryParams{}) + if switchPortStatuses_err != nil { + return nil, fmt.Errorf("Switch.GetOrganizationSwitchPortsBySwitch failed; [%d] %s. %w", switchPortStatuses_res.StatusCode(), switchPortStatuses_res.Body(), switchPortStatuses_err) + } + serialSwitchPortStatuses[Serial(serial)] = switchPortStatuses_val + + } + + return serialSwitchPortStatuses, nil +} + +func reportSwitchPortStatusBySerial(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, switchSerialPortStatuses map[Serial]*meraki_api.ResponseSwitchGetDeviceSwitchPortsStatuses) { + metrics := []mapstr.M{} + + for serial, portStatuses := range switchSerialPortStatuses { + for _, portStatus := range *portStatuses { + metric := mapstr.M{ + "switch.port.status.port_id": portStatus.PortID, + "switch.port.status.enabled": portStatus.Enabled, + "switch.port.status.status": portStatus.Status, + "switch.port.status.spanning_tree.statuses": portStatus.SpanningTree.Statuses, + "switch.port.status.is_uplink": portStatus.IsUplink, + "switch.port.status.errors": portStatus.Errors, + "switch.port.status.warnings": portStatus.Warnings, + "switch.port.status.speed": portStatus.Speed, + "switch.port.status.duplex": portStatus.Duplex, + "switch.port.status.usage_in_kb.sent": portStatus.UsageInKb.Sent, + "switch.port.status.usage_in_kb.recv": portStatus.UsageInKb.Recv, + "switch.port.status.usage_in_kb.total": portStatus.UsageInKb.Total, + "switch.port.status.client_count": portStatus.ClientCount, + "switch.port.status.power_usage_in_wh": portStatus.PowerUsageInWh, + "switch.port.status.traffic_in_kbps.total": portStatus.TrafficInKbps.Total, + "switch.port.status.traffic_in_kbps.sent": portStatus.TrafficInKbps.Sent, + "switch.port.status.traffic_in_kbps.recv": portStatus.TrafficInKbps.Recv, + "switch.port.status.secure_port.enabled": portStatus.SecurePort.Enabled, + "switch.port.status.secure_port.active": portStatus.SecurePort.Active, + "switch.port.status.secure_port.authentication_status": portStatus.SecurePort.AuthenticationStatus, + "switch.port.status.secure_port.config_overrides.type": portStatus.SecurePort.ConfigOverrides.Type, + "switch.port.status.secure_port.config_overrides.vlan": portStatus.SecurePort.ConfigOverrides.VLAN, + "switch.port.status.secure_port.config_overrides.voice_vlan": portStatus.SecurePort.ConfigOverrides.VoiceVLAN, + "switch.port.status.secure_port.config_overrides.allowed_vlans": portStatus.SecurePort.ConfigOverrides.AllowedVLANs, + // "switch.port.status.44": portStatus.poe.isAllocated // Missing on meraki go api + } + + if portStatus.Cdp != nil { + metric["switch.port.status.cdp.system_name"] = portStatus.Cdp.SystemName + metric["switch.port.status.cdp.platform"] = portStatus.Cdp.Platform + metric["switch.port.status.cdp.device_id"] = portStatus.Cdp.DeviceID + metric["switch.port.status.cdp.port_id"] = portStatus.Cdp.PortID + metric["switch.port.status.cdp.native_vlan"] = portStatus.Cdp.NativeVLAN + metric["switch.port.status.cdp.address"] = portStatus.Cdp.Address + metric["switch.port.status.cdp.management_address"] = portStatus.Cdp.ManagementAddress + metric["switch.port.status.cdp.version"] = portStatus.Cdp.Version + metric["switch.port.status.cdp.vtp_management_domain"] = portStatus.Cdp.VtpManagementDomain + metric["switch.port.status.cdp.capabilities"] = portStatus.Cdp.Capabilities + } + + if portStatus.Lldp != nil { + metric["switch.port.status.Lldp.system_name"] = portStatus.Lldp.SystemName + metric["switch.port.status.Lldp.system_description"] = portStatus.Lldp.SystemDescription + metric["switch.port.status.Lldp.chassis_id"] = portStatus.Lldp.ChassisID + metric["switch.port.status.Lldp.port_id"] = portStatus.Lldp.PortID + metric["switch.port.status.Lldp.management_vlan"] = portStatus.Lldp.ManagementVLAN + metric["switch.port.status.Lldp.port_vlan"] = portStatus.Lldp.PortVLAN + metric["switch.port.status.Lldp.management_address"] = portStatus.Lldp.ManagementAddress + metric["switch.port.status.Lldp.port_description"] = portStatus.Lldp.PortDescription + metric["switch.port.status.Lldp.system_capabilties"] = portStatus.Lldp.SystemCapabilities + } + + if device, ok := devices[Serial(serial)]; ok { + metric["device.address"] = device.Address + metric["device.firmware"] = device.Firmware + metric["device.imei"] = device.Imei + metric["device.lan_ip"] = device.LanIP + metric["device.location"] = device.Location + metric["device.mac"] = device.Mac + metric["device.model"] = device.Model + metric["device.name"] = device.Name + metric["device.network_id"] = device.NetworkID + metric["device.notes"] = device.Notes + metric["device.product_type"] = device.ProductType + metric["device.serial"] = device.Serial + metric["device.tags"] = device.Tags + + } + metrics = append(metrics, metric) + + } + } + + ReportMetricsForOrganization(reporter, organizationID, metrics) + +} diff --git a/x-pack/metricbeat/module/meraki/device_health/types.go b/x-pack/metricbeat/module/meraki/device_health/types.go index fd9978e0f36..a39a8029296 100644 --- a/x-pack/metricbeat/module/meraki/device_health/types.go +++ b/x-pack/metricbeat/module/meraki/device_health/types.go @@ -11,6 +11,7 @@ import ( // device unique identifier type Serial string +// network unique identifier type NetworkID string // Device contains static device attributes (i.e. dimensions) From 0c5b27f90ed3078ab8dc245d7231cb82bdcfae2a Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Tue, 10 Sep 2024 14:41:34 -0600 Subject: [PATCH 05/13] processing review comments --- x-pack/metricbeat/include/list.go | 3 +- .../meraki/device_health/device_health.go | 2 +- ...vice_network_health_channel_utilization.go | 28 ++++++++----------- ...ice_wireless_device_channel_utilization.go | 14 ++++++---- .../meraki/device_health/license_overview.go | 8 +++--- .../module/meraki/device_health/types.go | 21 -------------- 6 files changed, 27 insertions(+), 49 deletions(-) diff --git a/x-pack/metricbeat/include/list.go b/x-pack/metricbeat/include/list.go index b4b60ae1d1e..b13740cfe44 100644 --- a/x-pack/metricbeat/include/list.go +++ b/x-pack/metricbeat/include/list.go @@ -47,7 +47,8 @@ import ( _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/galley" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/mesh" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/mixer" - _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/pilot" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/istio/pilot" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/meraki/device_health" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/performance" diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go index 80dde438ad0..de92a53f59a 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_health.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -161,7 +161,7 @@ func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { if wireless_err != nil { return fmt.Errorf("GetOrganizationWirelessDevicesChannelUtilizationByDevice failed; [%d] %s. %w", wireless_res.StatusCode(), wireless_res.Body(), wireless_err) } - var wirelessDevices WirelessDevicesChannelUtilizationByDevice + var wirelessDevices *meraki_api.ResponseOrganizationsGetOrganizationWirelessDevicesChannelUtilizationByDevice unmashal_err := json.Unmarshal(wireless_res.Body(), &wirelessDevices) if unmashal_err != nil { return fmt.Errorf("device_network_health_channel_utilization json umarshal failed; %w", unmashal_err) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go index 44b371ba8d1..510c318aca7 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go @@ -6,7 +6,6 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) @@ -15,10 +14,8 @@ func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *mer var networkHealthUtilizations []*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization for _, network := range *networks { - for _, product_type := range network.ProductTypes { - - if strings.Compare(product_type, "wireless") == 0 { + if product_type == "wireless" { networkHealthUtilization, res, err := client.Networks.GetNetworkNetworkHealthChannelUtilization(network.ID, &meraki_api.GetNetworkNetworkHealthChannelUtilizationQueryParams{}) if err != nil { //"This endpoint is only available for networks on MR 27.0 or above." @@ -28,13 +25,13 @@ func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *mer return nil, fmt.Errorf("Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) } } else { + //fmt.Printf("\n#Added Networks.GetNetworkNetworkHealthChannelUtilization networkInfo; [%d] %s.\n###################", res.StatusCode(), res.Body()) networkHealthUtilizations = append(networkHealthUtilizations, networkHealthUtilization) } } } } - return networkHealthUtilizations, nil } @@ -42,7 +39,6 @@ func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationI metrics := []mapstr.M{} for _, networkHealthUtil := range networkHealthUtilizations { for _, network := range *networkHealthUtil { - metric := mapstr.M{ "network.health.channel.radio.serial": network.Serial, "network.health.channel.radio.model": network.Model, @@ -50,19 +46,19 @@ func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationI } for k, wifi0 := range *network.Wifi0 { - metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.start_time", k)] = wifi0.StartTime - metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.end_time", k)] = wifi0.EndTime - metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.utilization80211", k)] = wifi0.Utilization80211 - metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.utilizationNon80211", k)] = wifi0.UtilizationNon80211 - metric[fmt.Sprintf("network.health.channel.radio.wifi0.%d.utilizationTotal", k)] = wifi0.UtilizationTotal + metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.start_time", k)] = wifi0.StartTime + metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.end_time", k)] = wifi0.EndTime + metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.utilization80211", k)] = wifi0.Utilization80211 + metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.utilizationNon80211", k)] = wifi0.UtilizationNon80211 + metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.utilizationTotal", k)] = wifi0.UtilizationTotal } for k, wifi1 := range *network.Wifi1 { - metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.start_time", k)] = wifi1.StartTime - metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.end_time", k)] = wifi1.EndTime - metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.utilization80211", k)] = wifi1.Utilization80211 - metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.utilizationNon80211", k)] = wifi1.UtilizationNon80211 - metric[fmt.Sprintf("network.health.channel.radio.wifi1.%d.utilizationTotal", k)] = wifi1.UtilizationTotal + metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.start_time", k)] = wifi1.StartTime + metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.end_time", k)] = wifi1.EndTime + metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.utilization80211", k)] = wifi1.Utilization80211 + metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.utilizationNon80211", k)] = wifi1.UtilizationNon80211 + metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.utilizationTotal", k)] = wifi1.UtilizationTotal } if device, ok := devices[Serial(network.Serial)]; ok { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go index a2ee020b285..51d7c5cfafd 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go @@ -3,15 +3,17 @@ package device_health import ( "fmt" + "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) -func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, wirelessDevices WirelessDevicesChannelUtilizationByDevice) { +func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, wirelessDevices *meraki_api.ResponseOrganizationsGetOrganizationWirelessDevicesChannelUtilizationByDevice) { metrics := []mapstr.M{} - for _, wirelessDevice := range wirelessDevices { + for _, wirelessDevice := range *wirelessDevices { if device, ok := devices[Serial(wirelessDevice.Serial)]; ok { @@ -31,10 +33,10 @@ func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organization "device.tags": device.Tags, } - for _, v := range wirelessDevice.ByBand { - metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.wifi.percentage", v.Band)] = v.Wifi.Percentage - metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.nonwifi.percentage", v.Band)] = v.NonWifi.Percentage - metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.total.percentage", v.Band)] = v.Total.Percentage + for _, v := range *wirelessDevice.ByBand { + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.wifi.percentage", common.DeDot(v.Band))] = v.Wifi.Percentage + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.nonwifi.percentage", common.DeDot(v.Band))] = v.NonWifi.Percentage + metric[fmt.Sprintf("wireless.device.channel.utilization.band_%s.total.percentage", common.DeDot(v.Band))] = v.Total.Percentage } metrics = append(metrics, metric) diff --git a/x-pack/metricbeat/module/meraki/device_health/license_overview.go b/x-pack/metricbeat/module/meraki/device_health/license_overview.go index 6e39d6a2951..6a17bb9bc85 100644 --- a/x-pack/metricbeat/module/meraki/device_health/license_overview.go +++ b/x-pack/metricbeat/module/meraki/device_health/license_overview.go @@ -154,10 +154,10 @@ func reportLicenseMetrics(reporter mb.ReporterV2, organizationID string, cotermL ReportMetricsForOrganization(reporter, organizationID, []mapstr.M{ { - "license.systems_manager.active_seats": systemsManagerLicense.ActiveSeats, - "license.systems_manager.orgwideenrolled_devices": systemsManagerLicense.OrgwideEnrolledDevices, - "license.systems_manager.total_seats": systemsManagerLicense.TotalSeats, - "license.systems_manager.unassigned_seats": systemsManagerLicense.UnassignedSeats, + "license.systems_manager.active_seats": systemsManagerLicense.ActiveSeats, + "license.systems_manager.org_wide_enrolled_devices": systemsManagerLicense.OrgwideEnrolledDevices, + "license.systems_manager.total_seats": systemsManagerLicense.TotalSeats, + "license.systems_manager.unassigned_seats": systemsManagerLicense.UnassignedSeats, }, }) } diff --git a/x-pack/metricbeat/module/meraki/device_health/types.go b/x-pack/metricbeat/module/meraki/device_health/types.go index a39a8029296..bfd714e3a91 100644 --- a/x-pack/metricbeat/module/meraki/device_health/types.go +++ b/x-pack/metricbeat/module/meraki/device_health/types.go @@ -96,24 +96,3 @@ type DevicePerformanceScore struct { PerformanceScore float64 HttpStatusCode int } - -// Uplink contains static device uplink attributes; uplinks are always associated with a device -type WirelessDevicesChannelUtilizationByDevice []struct { - Serial Serial `json:"serial"` - Mac string `json:"mac"` - Network struct { - ID string `json:"id"` - } `json:"network"` - ByBand []struct { - Band string `json:"band"` - Wifi struct { - Percentage float64 `json:"percentage"` - } `json:"wifi"` - NonWifi struct { - Percentage float64 `json:"percentage"` - } `json:"nonWifi"` - Total struct { - Percentage float64 `json:"percentage"` - } `json:"total"` - } `json:"byBand"` -} From 0ffeed985a695c3e0e26b0cab792149c2de379d3 Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Wed, 11 Sep 2024 13:57:47 -0600 Subject: [PATCH 06/13] refactored for comments --- .../device_appliance_performance_score.go | 90 +++---------------- .../device_appliance_uplink_status_and_ha.go | 74 +++++++++------ .../device_cellular_gateway_uplink_status.go | 36 ++++---- .../meraki/device_health/device_health.go | 33 ++++--- .../meraki/device_health/device_info.go | 38 +------- .../meraki/device_health/device_license.go | 2 +- .../device_network_appliance_ports.go | 21 +++-- ...device_network_appliance_vpn_sitetosite.go | 2 +- ...vice_network_health_channel_utilization.go | 48 ++++++---- .../meraki/device_health/device_status.go | 14 ++- .../device_health/device_switch_port.go | 48 +++++----- .../device_switch_port_status.go | 2 +- .../device_uplink_loss_and_latency.go | 50 +---------- ...ice_wireless_device_channel_utilization.go | 2 +- .../meraki/device_health/license_overview.go | 2 +- .../module/meraki/device_health/types.go | 1 + 16 files changed, 180 insertions(+), 283 deletions(-) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go index 5dc8384db7b..c4e52ad0d3d 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go @@ -1,34 +1,33 @@ package device_health import ( - "encoding/json" "fmt" - "io" "strings" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/elastic-agent-libs/mapstr" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) -func getDevicePerformanceScores(url string, token string, devices map[Serial]*Device) (map[Serial]*Device, map[Serial]*DevicePerformanceScore, error) { +func getDevicePerformanceScores(client *meraki_api.Client, devices map[Serial]*Device) (map[Serial]*DevicePerformanceScore, error) { mx_devices := pruneDevicesForMxOnly(devices) scores := make(map[Serial]*DevicePerformanceScore) for _, device := range mx_devices { - perf_score, status_code, err := getDevicePerformanceScoresBySerialId(url, token, device.Serial) - if err != nil { - return nil, nil, fmt.Errorf("getDevicePerformanceScores -> getDevicePerformanceScoresBySerialId failed; %w", err) + score_val, score_res, score_err := client.Appliance.GetDeviceAppliancePerformance(device.Serial) + if score_err != nil { + return nil, fmt.Errorf("Appliance.GetDeviceAppliancePerformance failed; %w", score_err) } - scores[Serial(device.Serial)] = &DevicePerformanceScore{ - PerformanceScore: perf_score, - HttpStatusCode: status_code, + if score_res.StatusCode() != 204 { + scores[Serial(device.Serial)] = &DevicePerformanceScore{ + PerformanceScore: *score_val.PerfScore, + HttpStatusCode: 200, + } } } - return mx_devices, scores, nil + return scores, nil } func pruneDevicesForMxOnly(devices map[Serial]*Device) map[Serial]*Device { @@ -41,70 +40,3 @@ func pruneDevicesForMxOnly(devices map[Serial]*Device) map[Serial]*Device { } return mx_devices } - -func getDevicePerformanceScoresBySerialId(base_url string, token string, serial string) (float64, int, error) { - //https://developer.cisco.com/meraki/api-v1/get-device-appliance-performance/ - url := base_url + "/api/v1/devices/" + serial + "/appliance/performance" - - response, err := HttpGetRequestWithMerakiRetry(url, token, 5) - if err != nil { - return 0, 0, fmt.Errorf("getDevicePerformanceScoresBySerialId HttpGetRequestWithMerakiRetry failed; %w", err) - } - - if response.StatusCode == 204 { - return -1, response.StatusCode, nil - } - - responseData, err := io.ReadAll(response.Body) - if err != nil { - return 0, 0, fmt.Errorf("getDevicePerformanceScoresBySerialId io.ReadAll failed; %w", err) - } - - var responseObject PerfScore - err = json.Unmarshal(responseData, &responseObject) - if err != nil { - fmt.Printf("\nresponse.status=%d \nresponse.body=%s", response.StatusCode, response.Body) - fmt.Printf("\nresponseData\n %s", responseData) - return 0, 0, fmt.Errorf("getDevicePerformanceScoresBySerialId json.Unmarshal failed; %w", err) - } - - //var tmp_float float64 - tmp_float := responseObject.PerformanceScore - - return tmp_float, response.StatusCode, nil - -} - -func reportPerformanceScoreMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, devicePerformanceScores map[Serial]*DevicePerformanceScore) { - devicePerformanceScoreMetrics := []mapstr.M{} - for serial, device := range devices { - metric := mapstr.M{ - "device.address": device.Address, - "device.firmware": device.Firmware, - "device.imei": device.Imei, - "device.lan_ip": device.LanIP, - "device.location": device.Location, - "device.mac": device.Mac, - "device.model": device.Model, - "device.name": device.Name, - "device.network_id": device.NetworkID, - "device.notes": device.Notes, - "device.product_type": device.ProductType, - "device.serial": device.Serial, - "device.tags": device.Tags, - } - - if score, ok := devicePerformanceScores[serial]; ok { - if score.HttpStatusCode == 204 { - metric["device.performance.http_status_code"] = score.HttpStatusCode - } else { - metric["device.performance.score"] = score.PerformanceScore - } - - } - devicePerformanceScoreMetrics = append(devicePerformanceScoreMetrics, metric) - } - - ReportMetricsForOrganization(reporter, organizationID, devicePerformanceScoreMetrics) - -} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go index 32b5c4e8885..77c201c5c72 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go @@ -1,13 +1,15 @@ package device_health import ( + "fmt" + "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) -func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseApplianceUplinkStatuses *meraki_api.ResponseApplianceGetOrganizationApplianceUplinkStatuses) { +func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseApplianceUplinkStatuses *meraki_api.ResponseApplianceGetOrganizationApplianceUplinkStatuses, lossLatencyUplinks []*Uplink) { metrics := []mapstr.M{} @@ -15,34 +17,54 @@ func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string if device, ok := devices[Serial(uplink.Serial)]; ok { metric := mapstr.M{ - "appliance.uplink.high_availablity.enabled": uplink.HighAvailability.Enabled, - "appliance.uplink.high_availablity.role": uplink.HighAvailability.Role, - "appliance.uplink.last_reported_at": uplink.LastReportedAt, - "device.address": device.Address, - "device.firmware": device.Firmware, - "device.imei": device.Imei, - "device.lan_ip": device.LanIP, - "device.location": device.Location, - "device.mac": device.Mac, - "device.model": device.Model, - "device.name": device.Name, - "device.network_id": device.NetworkID, - "device.notes": device.Notes, - "device.product_type": device.ProductType, - "device.serial": device.Serial, - "device.tags": device.Tags, + "uplink.high_availablity.enabled": uplink.HighAvailability.Enabled, + "uplink.high_availablity.role": uplink.HighAvailability.Role, + "uplink.last_reported_at": uplink.LastReportedAt, + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v } for _, item := range *uplink.Uplinks { + + for _, lossLatencyMetric := range lossLatencyUplinks { + if lossLatencyMetric.Interface == item.Interface && string(lossLatencyMetric.DeviceSerial) == device.Serial && lossLatencyMetric.NetworkID == device.NetworkID { + for _, lossLatency := range lossLatencyMetric.Metrics { + //It seems there is bug in the client.Organizations.GetOrganizationDevicesUplinksLossAndLatency code returning differnt IP + //To mitigate, I am additionally printing the ip as seperate value, IMO it is odd these do not match. + // client.Appliance.GetOrganizationApplianceUplinkStatuses + metric["uplink.loss_latancy.ip"] = lossLatencyMetric.IP + metric["uplink.loss_latancy.@timestamp"] = lossLatency.Timestamp + metric["uplink.loss_latancy.loss_percent"] = lossLatency.LossPercent + metric["uplink.loss_latancy.latency_ms"] = lossLatency.LatencyMs + + } + } + + } metrics = append(metrics, mapstr.Union(metric, mapstr.M{ - "appliance.uplink.interface": item.Interface, - "appliance.uplink.status": item.Status, - "appliance.uplink.ip": item.IP, - "appliance.uplink.gateway": item.Gateway, - "appliance.uplink.public_ip": item.PublicIP, - "appliance.uplink.primary_dns": item.PrimaryDNS, - "appliance.uplink.secondary_dns": item.SecondaryDNS, - "appliance.uplink.ip_assigned_by": item.IPAssignedBy, + "uplink.interface": item.Interface, + "uplink.status": item.Status, + "uplink.ip": item.IP, + "uplink.gateway": item.Gateway, + "uplink.public_ip": item.PublicIP, + "uplink.primary_dns": item.PrimaryDNS, + "uplink.secondary_dns": item.SecondaryDNS, + "uplink.ip_assigned_by": item.IPAssignedBy, })) } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go b/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go index 7d19c2baa11..776aa57f2e9 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go @@ -1,12 +1,10 @@ package device_health import ( - "fmt" - "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func reportCellularGatewayApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseCellularGatewayUplinkStatuses *meraki_api.ResponseCellularGatewayGetOrganizationCellularGatewayUplinkStatuses) { @@ -34,23 +32,23 @@ func reportCellularGatewayApplianceUplinkStatuses(reporter mb.ReporterV2, organi "device.tags": device.Tags, } - for i, item := range *uplink.Uplinks { + for _, item := range *uplink.Uplinks { metrics = append(metrics, mapstr.Union(metric, mapstr.M{ - fmt.Sprintf("cellular.gateway.uplink.item_%d.apn", i): item.Apn, - fmt.Sprintf("cellular.gateway.uplink.item_%d.connection_type", i): item.ConnectionType, - fmt.Sprintf("cellular.gateway.uplink.item_%d.dns1", i): item.DNS1, - fmt.Sprintf("cellular.gateway.uplink.item_%d.dns2", i): item.DNS2, - fmt.Sprintf("cellular.gateway.uplink.item_%d.gateway", i): item.Gateway, - fmt.Sprintf("cellular.gateway.uplink.item_%d.iccid", i): item.Iccid, - fmt.Sprintf("cellular.gateway.uplink.item_%d.interface", i): item.Interface, - fmt.Sprintf("cellular.gateway.uplink.item_%d.ip", i): item.IP, - fmt.Sprintf("cellular.gateway.uplink.item_%d.model", i): item.Model, - fmt.Sprintf("cellular.gateway.uplink.item_%d.provider", i): item.Provider, - fmt.Sprintf("cellular.gateway.uplink.item_%d.public_ip", i): item.PublicIP, - fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_stat.rsrp", i): item.SignalStat.Rsrp, - fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_stat.rsrq", i): item.SignalStat.Rsrq, - fmt.Sprintf("cellular.gateway.uplink.item_%d.signal_type", i): item.SignalType, - fmt.Sprintf("cellular.gateway.uplink.item_%d.status", i): item.Status, + "cellular.gateway.uplink.apn": item.Apn, + "cellular.gateway.uplink.connection_type": item.ConnectionType, + "cellular.gateway.uplink.dns1": item.DNS1, + "cellular.gateway.uplink.dns2": item.DNS2, + "cellular.gateway.uplink.gateway": item.Gateway, + "cellular.gateway.uplink.iccid": item.Iccid, + "cellular.gateway.uplink.interface": item.Interface, + "cellular.gateway.uplink.ip": item.IP, + "cellular.gateway.uplink.model": item.Model, + "cellular.gateway.uplink.provider": item.Provider, + "cellular.gateway.uplink.public_ip": item.PublicIP, + "cellular.gateway.uplink.signal_stat.rsrp": item.SignalStat.Rsrp, + "cellular.gateway.uplink.signal_stat.rsrq": item.SignalStat.Rsrq, + "cellular.gateway.uplink.signal_type": item.SignalType, + "cellular.gateway.uplink.status": item.Status, })) } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go index de92a53f59a..324a43812c5 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_health.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -18,7 +18,7 @@ import ( "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) // init registers the MetricSet with the central registry as soon as the program @@ -106,14 +106,25 @@ func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { if err != nil { return fmt.Errorf("getDeviceStatuses() failed; %w", err) } - reportDeviceStatusMetrics(reporter, org, devices, deviceStatuses) + //Get mx device performance score + mx_scores, err := getDevicePerformanceScores(m.client, devices) + if err != nil { + return fmt.Errorf("getDevicePerformanceScores() failed; %w", err) + } + reportDeviceStatusMetrics(reporter, org, devices, deviceStatuses, mx_scores) + + // //Get & Report Organization Appliance Uplink + appliance_val, appliance_res, appliance_err := m.client.Appliance.GetOrganizationApplianceUplinkStatuses(org, &meraki_api.GetOrganizationApplianceUplinkStatusesQueryParams{}) + if appliance_err != nil { + return fmt.Errorf("Appliance.GetOrganizationApplianceUplinkStatuses failed; [%d] %s. %w", appliance_res.StatusCode(), appliance_res.Body(), appliance_err) + } //Get & Report Device Uplink Status - uplinks, err := getDeviceUplinkMetrics(m.client, org, m.BaseMetricSet.Module().Config().Period) + lossLatencyuplinks, err := getDeviceUplinkLossLatencyMetrics(m.client, org, m.BaseMetricSet.Module().Config().Period) if err != nil { return fmt.Errorf("getDeviceUplinkMetrics() failed; %w", err) } - reportDeviceUplinkMetrics(reporter, org, devices, uplinks) + reportApplianceUplinkStatuses(reporter, org, devices, appliance_val, lossLatencyuplinks) //Get & Report Device License State cotermLicenses, perDeviceLicenses, systemsManagerLicense, err := getLicenseStates(m.client, org) @@ -122,13 +133,6 @@ func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { } reportLicenseMetrics(reporter, org, cotermLicenses, perDeviceLicenses, systemsManagerLicense) - //Get mx device performance score - mx_devices, mx_scores, err := getDevicePerformanceScores(m.meraki_url, m.meraki_apikey, devices) - if err != nil { - return fmt.Errorf("getDevicePerformanceScores() failed; %w", err) - } - reportPerformanceScoreMetrics(reporter, org, mx_devices, mx_scores) - //Get & Report Org Celluar Uplink Status cullular_val, cullular_res, cullular_err := m.client.CellularGateway.GetOrganizationCellularGatewayUplinkStatuses(org, &meraki_api.GetOrganizationCellularGatewayUplinkStatusesQueryParams{}) if cullular_err != nil { @@ -136,13 +140,6 @@ func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { } reportCellularGatewayApplianceUplinkStatuses(reporter, org, devices, cullular_val) - //Get & Report Organization Appliance Uplink - appliance_val, appliance_res, appliance_err := m.client.Appliance.GetOrganizationApplianceUplinkStatuses(org, &meraki_api.GetOrganizationApplianceUplinkStatusesQueryParams{}) - if appliance_err != nil { - return fmt.Errorf("Appliance.GetOrganizationApplianceUplinkStatuses failed; [%d] %s. %w", appliance_res.StatusCode(), appliance_res.Body(), appliance_err) - } - reportApplianceUplinkStatuses(reporter, org, devices, appliance_val) - //Get Org Networks //Get Network Health by Org Network //Report NetworkHalthChannelUtilization diff --git a/x-pack/metricbeat/module/meraki/device_health/device_info.go b/x-pack/metricbeat/module/meraki/device_health/device_info.go index bd6b69e5337..7b9f86a3cbf 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_info.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_info.go @@ -7,45 +7,9 @@ package device_health import ( "fmt" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) -func GetDevicesByProductType(client *meraki_api.Client, organizationID string, productTypes []string) (map[Serial]*Device, error) { - val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{ - ProductTypes: productTypes, - }) - - if err != nil { - return nil, fmt.Errorf("GetOrganizationDevices failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) - } - - devices := make(map[Serial]*Device) - for _, d := range *val { - device := Device{ - Firmware: d.Firmware, - Imei: d.Imei, - LanIP: d.LanIP, - Location: []*float64{d.Lng, d.Lat}, // (lon, lat) order is important! - Mac: d.Mac, - Model: d.Model, - Name: d.Name, - NetworkID: d.NetworkID, - Notes: d.Notes, - ProductType: d.ProductType, - Serial: d.Serial, - Tags: d.Tags, - } - if d.Details != nil { - for _, detail := range *d.Details { - device.Details[detail.Name] = detail.Value - } - } - devices[Serial(device.Serial)] = &device - } - - return devices, nil -} - func GetDevices(client *meraki_api.Client, organizationID string) (map[Serial]*Device, error) { val, res, err := client.Organizations.GetOrganizationDevices(organizationID, &meraki_api.GetOrganizationDevicesQueryParams{}) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_license.go b/x-pack/metricbeat/module/meraki/device_health/device_license.go index bdb398cd938..33a9f9675d3 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_license.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_license.go @@ -4,7 +4,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func reportOrganizationDeviceLicenses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, orgLicenseBySerial *meraki_api.ResponseOrganizationsGetOrganizationLicenses) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go index 9a767a9fcf4..654b3d5f64a 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func getNetworkAppliancePorts(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) (map[NetworkID]*meraki_api.ResponseApplianceGetNetworkAppliancePorts, error) { @@ -55,18 +55,17 @@ func reportNetwrokAppliancePorts(reporter mb.ReporterV2, organizationID string, "device.tags": device.Tags, } - for i, port := range *networkPort { - metric[fmt.Sprintf("appliance.network.port.item_%d.number", i)] = port.Number - metric[fmt.Sprintf("appliance.network.port.item_%d.enabled", i)] = port.Enabled - metric[fmt.Sprintf("appliance.network.port.item_%d.type", i)] = port.Type - metric[fmt.Sprintf("appliance.network.port.item_%d.drop_untagged_traffic", i)] = port.DropUntaggedTraffic - metric[fmt.Sprintf("appliance.network.port.item_%d.vlan", i)] = port.VLAN - metric[fmt.Sprintf("appliance.network.port.item_%d.allowed_vlans", i)] = port.AllowedVLANs - metric[fmt.Sprintf("appliance.network.port.item_%d.access_policy", i)] = port.AccessPolicy + for _, port := range *networkPort { + metric["appliance.network.port.number"] = port.Number + metric["appliance.network.port.enabled"] = port.Enabled + metric["appliance.network.port.type"] = port.Type + metric["appliance.network.port.drop_untagged_traffic"] = port.DropUntaggedTraffic + metric["appliance.network.port.vlan"] = port.VLAN + metric["appliance.network.port.allowed_vlans"] = port.AllowedVLANs + metric["appliance.network.port.access_policy"] = port.AccessPolicy + metrics = append(metrics, metric) } - metrics = append(metrics, metric) - } } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go index 325f43d4ff8..6ffd9b577ec 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func getNetworkApplianceVPNSiteToSite(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) (map[NetworkID]*meraki_api.ResponseApplianceGetNetworkApplianceVpnSiteToSiteVpn, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go index 510c318aca7..43f13b2a6c5 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) ([]*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization, error) { @@ -39,28 +39,16 @@ func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationI metrics := []mapstr.M{} for _, networkHealthUtil := range networkHealthUtilizations { for _, network := range *networkHealthUtil { + + wifi0_encountered := false + wifi1_encountered := false + metric := mapstr.M{ "network.health.channel.radio.serial": network.Serial, "network.health.channel.radio.model": network.Model, "network.health.channel.radio.tags": network.Tags, } - for k, wifi0 := range *network.Wifi0 { - metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.start_time", k)] = wifi0.StartTime - metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.end_time", k)] = wifi0.EndTime - metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.utilization80211", k)] = wifi0.Utilization80211 - metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.utilizationNon80211", k)] = wifi0.UtilizationNon80211 - metric[fmt.Sprintf("network.health.channel.radio.wifi0.item_%d.utilizationTotal", k)] = wifi0.UtilizationTotal - } - - for k, wifi1 := range *network.Wifi1 { - metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.start_time", k)] = wifi1.StartTime - metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.end_time", k)] = wifi1.EndTime - metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.utilization80211", k)] = wifi1.Utilization80211 - metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.utilizationNon80211", k)] = wifi1.UtilizationNon80211 - metric[fmt.Sprintf("network.health.channel.radio.wifi1.item_%d.utilizationTotal", k)] = wifi1.UtilizationTotal - } - if device, ok := devices[Serial(network.Serial)]; ok { metric["device.address"] = device.Address metric["device.firmware"] = device.Firmware @@ -77,7 +65,31 @@ func reportNetworkHealthChannelUtilization(reporter mb.ReporterV2, organizationI metric["device.tags"] = device.Tags } - metrics = append(metrics, metric) + + for _, wifi0 := range *network.Wifi0 { + metric["network.health.channel.radio.wifi0.start_time"] = wifi0.StartTime + metric["network.health.channel.radio.wifi0.end_time"] = wifi0.EndTime + metric["network.health.channel.radio.wifi0.utilization80211"] = wifi0.Utilization80211 + metric["network.health.channel.radio.wifi0.utilizationNon80211"] = wifi0.UtilizationNon80211 + metric["network.health.channel.radio.wifi0.utilizationTotal"] = wifi0.UtilizationTotal + wifi0_encountered = true + metrics = append(metrics, metric) + } + + for _, wifi1 := range *network.Wifi1 { + metric["network.health.channel.radio.wifi1.start_time"] = wifi1.StartTime + metric["network.health.channel.radio.wifi1.end_time"] = wifi1.EndTime + metric["network.health.channel.radio.wifi1.utilization80211"] = wifi1.Utilization80211 + metric["network.health.channel.radio.wifi1.utilizationNon80211"] = wifi1.UtilizationNon80211 + metric["network.health.channel.radio.wifi1.utilizationTotal"] = wifi1.UtilizationTotal + wifi1_encountered = true + metrics = append(metrics, metric) + } + + if !wifi0_encountered && !wifi1_encountered { + metrics = append(metrics, metric) + } + } } ReportMetricsForOrganization(reporter, organizationID, metrics) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_status.go b/x-pack/metricbeat/module/meraki/device_health/device_status.go index c076933cefd..9afdff52042 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_status.go @@ -9,7 +9,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func getDeviceStatuses(client *meraki_api.Client, organizationID string) (map[Serial]*DeviceStatus, error) { @@ -35,7 +35,7 @@ func getDeviceStatuses(client *meraki_api.Client, organizationID string) (map[Se return statuses, nil } -func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, deviceStatuses map[Serial]*DeviceStatus) { +func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, deviceStatuses map[Serial]*DeviceStatus, devicePerformanceScores map[Serial]*DevicePerformanceScore) { deviceStatusMetrics := []mapstr.M{} for serial, device := range devices { metric := mapstr.M{ @@ -67,6 +67,16 @@ func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, de metric["device.status.secondary_dns"] = status.SecondaryDNS metric["device.status.status"] = status.Status } + + if score, ok := devicePerformanceScores[serial]; ok { + if score.HttpStatusCode == 204 { + metric["device.performance.http_status_code"] = score.HttpStatusCode + } else { + metric["device.performance.score"] = score.PerformanceScore + } + + } + deviceStatusMetrics = append(deviceStatusMetrics, metric) } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go b/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go index 2928e14474c..65876947201 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go @@ -1,12 +1,10 @@ package device_health import ( - "fmt" - "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func reportOrganizationDeviceSwitchPortBySwitch(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, orgSwitchPortsBySwitch *meraki_api.ResponseSwitchGetOrganizationSwitchPortsBySwitch) { @@ -15,6 +13,8 @@ func reportOrganizationDeviceSwitchPortBySwitch(reporter mb.ReporterV2, organiza for _, switchPort := range *orgSwitchPortsBySwitch { + port_encountered := false + metric := mapstr.M{ "switch.port.name": switchPort.Name, "switch.port.serial": switchPort.Serial, @@ -24,23 +24,6 @@ func reportOrganizationDeviceSwitchPortBySwitch(reporter mb.ReporterV2, organiza "switch.port.model": switchPort.Model, } - for i, port := range *switchPort.Ports { - metric[fmt.Sprintf("switch.port.item_%d.port_id", i)] = port.PortID - metric[fmt.Sprintf("switch.port.item_%d.name", i)] = port.Name - metric[fmt.Sprintf("switch.port.item_%d.tags", i)] = port.Tags - metric[fmt.Sprintf("switch.port.item_%d.enabled", i)] = port.Enabled - metric[fmt.Sprintf("switch.port.item_%d.poe_enabled", i)] = port.PoeEnabled - metric[fmt.Sprintf("switch.port.item_%d.vlan", i)] = port.VLAN - metric[fmt.Sprintf("switch.port.item_%d.voice_vlan", i)] = port.VoiceVLAN - metric[fmt.Sprintf("switch.port.item_%d.allowed_vlans", i)] = port.AllowedVLANs - metric[fmt.Sprintf("switch.port.item_%d.rstp_enabled", i)] = port.RstpEnabled - metric[fmt.Sprintf("switch.port.item_%d.stp_guard", i)] = port.StpGuard - metric[fmt.Sprintf("switch.port.item_%d.link_negotiation", i)] = port.LinkNegotiation - metric[fmt.Sprintf("switch.port.item_%d.access_policy_type", i)] = port.AccessPolicyType - metric[fmt.Sprintf("switch.port.item_%d.sticky_mac_allow_list", i)] = port.StickyMacAllowList - metric[fmt.Sprintf("switch.port.item_%d.sticky_mac_allow_list_limit", i)] = port.StickyMacAllowListLimit - } - if device, ok := devices[Serial(switchPort.Serial)]; ok { metric["device.address"] = device.Address metric["device.firmware"] = device.Firmware @@ -57,7 +40,30 @@ func reportOrganizationDeviceSwitchPortBySwitch(reporter mb.ReporterV2, organiza metric["device.tags"] = device.Tags } - metrics = append(metrics, metric) + + for _, port := range *switchPort.Ports { + metric["switch.port.port_id"] = port.PortID + metric["switch.port.name"] = port.Name + metric["switch.port.tags"] = port.Tags + metric["switch.port.enabled"] = port.Enabled + metric["switch.port.poe_enabled"] = port.PoeEnabled + metric["switch.port.vlan"] = port.VLAN + metric["switch.port.voice_vlan"] = port.VoiceVLAN + metric["switch.port.allowed_vlans"] = port.AllowedVLANs + metric["switch.port.rstp_enabled"] = port.RstpEnabled + metric["switch.port.stp_guard"] = port.StpGuard + metric["switch.port.link_negotiation"] = port.LinkNegotiation + metric["switch.port.access_policy_type"] = port.AccessPolicyType + metric["switch.port.sticky_mac_allow_list"] = port.StickyMacAllowList + metric["switch.port.sticky_mac_allow_list_limit"] = port.StickyMacAllowListLimit + port_encountered = true + metrics = append(metrics, metric) + } + + if !port_encountered { + metrics = append(metrics, metric) + } + } ReportMetricsForOrganization(reporter, organizationID, metrics) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go b/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go index 82f7e49100f..34abc802b15 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go @@ -5,7 +5,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func getSwitchPortStatusBySerial(client *meraki_api.Client, org string) (map[Serial]*meraki_api.ResponseSwitchGetDeviceSwitchPortsStatuses, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go index d36b7a2bb6e..3ad6235e597 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go @@ -8,13 +8,10 @@ import ( "fmt" "time" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/elastic-agent-libs/mapstr" - - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) -func getDeviceUplinkMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { +func getDeviceUplinkLossLatencyMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { val, res, err := client.Organizations.GetOrganizationDevicesUplinksLossAndLatency( organizationID, &meraki_api.GetOrganizationDevicesUplinksLossAndLatencyQueryParams{ @@ -33,6 +30,7 @@ func getDeviceUplinkMetrics(client *meraki_api.Client, organizationID string, pe DeviceSerial: Serial(device.Serial), IP: device.IP, Interface: device.Uplink, + NetworkID: device.NetworkID, } for _, measurement := range *device.TimeSeries { @@ -60,45 +58,3 @@ func getDeviceUplinkMetrics(client *meraki_api.Client, organizationID string, pe return uplinks, nil } - -func reportDeviceUplinkMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, uplinks []*Uplink) { - metrics := []mapstr.M{} - - for _, uplink := range uplinks { - if device, ok := devices[uplink.DeviceSerial]; ok { - metric := mapstr.M{ - "uplink.ip": uplink.IP, - "uplink.interface": uplink.Interface, - // fixme: repeated code serializing device metadata to mapstr - "device.address": device.Address, - "device.firmware": device.Firmware, - "device.imei": device.Imei, - "device.lan_ip": device.LanIP, - "device.location": device.Location, - "device.mac": device.Mac, - "device.model": device.Model, - "device.name": device.Name, - "device.network_id": device.NetworkID, - "device.notes": device.Notes, - "device.product_type": device.ProductType, - "device.serial": device.Serial, - "device.tags": device.Tags, - } - - for k, v := range device.Details { - metric[fmt.Sprintf("device.details.%s", k)] = v - } - - for _, uplinkMetric := range uplink.Metrics { - metrics = append(metrics, mapstr.Union(metric, mapstr.M{ - "@timestamp": uplinkMetric.Timestamp, - "uplink.loss_percent": uplinkMetric.LossPercent, - "uplink.latency_ms": uplinkMetric.LatencyMs, - })) - } - } else { - // missing device metadata; ignore - } - } - ReportMetricsForOrganization(reporter, organizationID, metrics) -} diff --git a/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go index 51d7c5cfafd..8b5131fa83e 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, wirelessDevices *meraki_api.ResponseOrganizationsGetOrganizationWirelessDevicesChannelUtilizationByDevice) { diff --git a/x-pack/metricbeat/module/meraki/device_health/license_overview.go b/x-pack/metricbeat/module/meraki/device_health/license_overview.go index 6a17bb9bc85..ab2ba382e5e 100644 --- a/x-pack/metricbeat/module/meraki/device_health/license_overview.go +++ b/x-pack/metricbeat/module/meraki/device_health/license_overview.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" + meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" ) func getLicenseStates(client *meraki_api.Client, organizationID string) ([]*CoterminationLicense, []*PerDeviceLicense, *SystemsManagerLicense, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/types.go b/x-pack/metricbeat/module/meraki/device_health/types.go index bfd714e3a91..6e94ab9c391 100644 --- a/x-pack/metricbeat/module/meraki/device_health/types.go +++ b/x-pack/metricbeat/module/meraki/device_health/types.go @@ -48,6 +48,7 @@ type Uplink struct { DeviceSerial Serial IP string Interface string + NetworkID string Metrics []*UplinkMetric } From 92232151fb2de3b2e63e46c050acf83742edd5aa Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Wed, 11 Sep 2024 14:22:02 -0600 Subject: [PATCH 07/13] fixing default metricset --- x-pack/metricbeat/modules.d/meraki.yml.disabled | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/metricbeat/modules.d/meraki.yml.disabled b/x-pack/metricbeat/modules.d/meraki.yml.disabled index f0770e68a3f..321af56e102 100644 --- a/x-pack/metricbeat/modules.d/meraki.yml.disabled +++ b/x-pack/metricbeat/modules.d/meraki.yml.disabled @@ -12,4 +12,3 @@ Accept: application/json apiKey: organizations: ["orgId"] - From d2e5848659ec903876c098d0d5e9c8c67ad2a147 Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Wed, 11 Sep 2024 14:26:28 -0600 Subject: [PATCH 08/13] Removing unused variables and adding text to required variables --- x-pack/metricbeat/modules.d/meraki.yml.disabled | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/x-pack/metricbeat/modules.d/meraki.yml.disabled b/x-pack/metricbeat/modules.d/meraki.yml.disabled index 321af56e102..3b45c487b97 100644 --- a/x-pack/metricbeat/modules.d/meraki.yml.disabled +++ b/x-pack/metricbeat/modules.d/meraki.yml.disabled @@ -5,10 +5,7 @@ metricsets: ["device_health"] enabled: true period: 60s - hosts: ["https://api.meraki.com"] - # This can be used for token based authorization: - bearer_token_file: /var/run/secrets/meraki/token - headers: - Accept: application/json + # The Meraki API key for your organization. apiKey: - organizations: ["orgId"] + # A list of organization id's to harvest meraki data. + organizations: [""] From d438a84477126b17de05c87a7990519a33b75f8a Mon Sep 17 00:00:00 2001 From: tommyers-elastic <106530686+tommyers-elastic@users.noreply.github.com> Date: Thu, 12 Sep 2024 10:11:42 +0100 Subject: [PATCH 09/13] add go module deps --- go.mod | 5 +++++ go.sum | 15 +++++++++++++++ .../device_appliance_performance_score.go | 2 +- .../device_appliance_uplink_status_and_ha.go | 2 +- .../device_cellular_gateway_uplink_status.go | 2 +- .../module/meraki/device_health/device_health.go | 2 +- .../module/meraki/device_health/device_info.go | 2 +- .../module/meraki/device_health/device_license.go | 2 +- .../device_network_appliance_ports.go | 2 +- .../device_network_appliance_vpn_sitetosite.go | 2 +- .../device_network_health_channel_utilization.go | 2 +- .../module/meraki/device_health/device_status.go | 2 +- .../meraki/device_health/device_switch_port.go | 2 +- .../device_health/device_switch_port_status.go | 2 +- .../device_uplink_loss_and_latency.go | 2 +- .../device_wireless_device_channel_utilization.go | 2 +- .../meraki/device_health/license_overview.go | 2 +- 17 files changed, 35 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 56318068f27..d180fb50d89 100644 --- a/go.mod +++ b/go.mod @@ -209,6 +209,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/icholy/digest v0.1.22 + github.com/meraki/dashboard-api-go/v3 v3.0.9 github.com/otiai10/copy v1.12.0 github.com/pierrec/lz4/v4 v4.1.18 github.com/pkg/xattr v0.4.9 @@ -290,6 +291,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-resty/resty/v2 v2.11.0 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/gobuffalo/here v0.6.7 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -300,6 +302,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/licenseclassifier v0.0.0-20221004142553-c1ed8fcf4bab // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -319,6 +322,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/juju/ratelimit v1.0.2 // indirect github.com/karrick/godirwalk v1.17.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/asmfmt v1.3.2 // indirect @@ -408,5 +412,6 @@ replace ( github.com/golang/glog => github.com/elastic/glog v1.0.1-0.20210831205241-7d8b5c89dfc4 github.com/google/gopacket => github.com/elastic/gopacket v1.1.20-0.20211202005954-d412fca7f83a github.com/insomniacslk/dhcp => github.com/elastic/dhcp v0.0.0-20200227161230-57ec251c7eb3 // indirect + github.com/meraki/dashboard-api-go/v3 => github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240910133632-85f77552ef82 github.com/snowflakedb/gosnowflake => github.com/snowflakedb/gosnowflake v1.6.19 ) diff --git a/go.sum b/go.sum index df15b7c4791..0161143c5c6 100644 --- a/go.sum +++ b/go.sum @@ -806,6 +806,8 @@ github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= +github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= +github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -958,6 +960,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -1183,6 +1187,8 @@ github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= +github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1291,6 +1297,8 @@ github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4f github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/meraki/dashboard-api-go/v3 v3.0.9 h1:l3VIHu+0Jy1GysHe2sSLxp+wVhY6EB2Ng3e8/ygVBXE= +github.com/meraki/dashboard-api-go/v3 v3.0.9/go.mod h1:ngZmCzPKto29KbttUw7ibrLc+5RHHBKihYFkWFrL1zE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -1629,6 +1637,8 @@ github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYa github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240910133632-85f77552ef82 h1:HkNKMd1VHxkKojkmXumIFCucJ81IDrW+Fb0/gjavtVA= +github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240910133632-85f77552ef82/go.mod h1:PS7t/0eK4QVh1lcUTJB/WL9B/Wp8dAHECGWV59f9IY0= github.com/tsg/go-daemon v0.0.0-20200207173439-e704b93fd89b h1:X/8hkb4rQq3+QuOxpJK7gWmAXmZucF0EI1s1BfBLq6U= github.com/tsg/go-daemon v0.0.0-20200207173439-e704b93fd89b/go.mod h1:jAqhj/JBVC1PwcLTWd6rjQyGyItxxrhpiBl8LSuAGmw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -1816,6 +1826,7 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= @@ -1958,6 +1969,7 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= @@ -2110,6 +2122,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -2123,6 +2136,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= @@ -2153,6 +2167,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go index c4e52ad0d3d..67defc869a5 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func getDevicePerformanceScores(client *meraki_api.Client, devices map[Serial]*Device) (map[Serial]*DevicePerformanceScore, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go index 77c201c5c72..684fcdb8c2a 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseApplianceUplinkStatuses *meraki_api.ResponseApplianceGetOrganizationApplianceUplinkStatuses, lossLatencyUplinks []*Uplink) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go b/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go index 776aa57f2e9..ea37f2b6004 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_cellular_gateway_uplink_status.go @@ -4,7 +4,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func reportCellularGatewayApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, responseCellularGatewayUplinkStatuses *meraki_api.ResponseCellularGatewayGetOrganizationCellularGatewayUplinkStatuses) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go index 324a43812c5..0e2c46b5b3a 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_health.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -18,7 +18,7 @@ import ( "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) // init registers the MetricSet with the central registry as soon as the program diff --git a/x-pack/metricbeat/module/meraki/device_health/device_info.go b/x-pack/metricbeat/module/meraki/device_health/device_info.go index 7b9f86a3cbf..a57b439f33b 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_info.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_info.go @@ -7,7 +7,7 @@ package device_health import ( "fmt" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func GetDevices(client *meraki_api.Client, organizationID string) (map[Serial]*Device, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_license.go b/x-pack/metricbeat/module/meraki/device_health/device_license.go index 33a9f9675d3..bdb398cd938 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_license.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_license.go @@ -4,7 +4,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func reportOrganizationDeviceLicenses(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, orgLicenseBySerial *meraki_api.ResponseOrganizationsGetOrganizationLicenses) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go index 654b3d5f64a..ac023f4e329 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_ports.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func getNetworkAppliancePorts(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) (map[NetworkID]*meraki_api.ResponseApplianceGetNetworkAppliancePorts, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go index 6ffd9b577ec..325f43d4ff8 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func getNetworkApplianceVPNSiteToSite(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) (map[NetworkID]*meraki_api.ResponseApplianceGetNetworkApplianceVpnSiteToSiteVpn, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go index 43f13b2a6c5..c5c8f890fbe 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *meraki_api.ResponseOrganizationsGetOrganizationNetworks) ([]*meraki_api.ResponseNetworksGetNetworkNetworkHealthChannelUtilization, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_status.go b/x-pack/metricbeat/module/meraki/device_health/device_status.go index 9afdff52042..9928a596519 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_status.go @@ -9,7 +9,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func getDeviceStatuses(client *meraki_api.Client, organizationID string) (map[Serial]*DeviceStatus, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go b/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go index 65876947201..d77abb99249 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_switch_port.go @@ -4,7 +4,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func reportOrganizationDeviceSwitchPortBySwitch(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, orgSwitchPortsBySwitch *meraki_api.ResponseSwitchGetOrganizationSwitchPortsBySwitch) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go b/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go index 34abc802b15..82f7e49100f 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_switch_port_status.go @@ -5,7 +5,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func getSwitchPortStatusBySerial(client *meraki_api.Client, org string) (map[Serial]*meraki_api.ResponseSwitchGetDeviceSwitchPortsStatuses, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go index 3ad6235e597..aa6c2048089 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func getDeviceUplinkLossLatencyMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go index 8b5131fa83e..51d7c5cfafd 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_wireless_device_channel_utilization.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func reportWirelessDeviceChannelUtilization(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, wirelessDevices *meraki_api.ResponseOrganizationsGetOrganizationWirelessDevicesChannelUtilizationByDevice) { diff --git a/x-pack/metricbeat/module/meraki/device_health/license_overview.go b/x-pack/metricbeat/module/meraki/device_health/license_overview.go index ab2ba382e5e..6a17bb9bc85 100644 --- a/x-pack/metricbeat/module/meraki/device_health/license_overview.go +++ b/x-pack/metricbeat/module/meraki/device_health/license_overview.go @@ -6,7 +6,7 @@ import ( "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/elastic-agent-libs/mapstr" - meraki_api "github.com/tommyers-elastic/dashboard-api-go/v3/sdk" + meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) func getLicenseStates(client *meraki_api.Client, organizationID string) ([]*CoterminationLicense, []*PerDeviceLicense, *SystemsManagerLicense, error) { From 998936fa077b071760d2c6034751b879f471dd0f Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Thu, 12 Sep 2024 08:34:54 -0600 Subject: [PATCH 10/13] fixing for loop and starting on fields yml --- .../metricbeat/module/meraki/_meta/fields.yml | 6 ++- .../meraki/device_health/_meta/fields.yml | 13 +++++-- .../meraki/device_health/device_health.go | 38 +------------------ ...device_network_appliance_vpn_sitetosite.go | 23 +++++++---- .../meraki/device_health/device_status.go | 2 +- 5 files changed, 32 insertions(+), 50 deletions(-) diff --git a/x-pack/metricbeat/module/meraki/_meta/fields.yml b/x-pack/metricbeat/module/meraki/_meta/fields.yml index cb687419f88..59b651d0fc4 100644 --- a/x-pack/metricbeat/module/meraki/_meta/fields.yml +++ b/x-pack/metricbeat/module/meraki/_meta/fields.yml @@ -2,9 +2,11 @@ title: "meraki" release: beta description: > - meraki module + Meraki module collects metrics from the meraki api fields: - name: meraki type: group - description: > + description: > + Various Meraki metrics fields: + diff --git a/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml b/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml index b0e83e2959d..2d66e605a1b 100644 --- a/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml +++ b/x-pack/metricbeat/module/meraki/device_health/_meta/fields.yml @@ -2,9 +2,14 @@ type: group release: beta description: > - device_health + meraki device_health metricset to collect device metrics fields: - - name: example - type: keyword + - name: device + type: group description: > - Example field + meraki devices metrics + fields: + - name: serial + type: number + description: > + unique device serial diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go index 0e2c46b5b3a..29b470bdeaa 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_health.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -7,9 +7,6 @@ package device_health import ( "encoding/json" "fmt" - "log" - "net/http" - "strconv" "time" "github.com/elastic/beats/v7/libbeat/common/cfgwarn" @@ -220,41 +217,10 @@ func ReportMetricsForOrganization(reporter mb.ReporterV2, organizationID string, event.Timestamp = ts delete(metric, "@timestamp") } - event.ModuleFields.Update(metric) - reporter.Event(event) - } - } -} - -func HttpGetRequestWithMerakiRetry(url string, token string, retry int) (*http.Response, error) { - //https://developer.cisco.com/meraki/api-v1/get-device-appliance-performance/ - - // Create a Bearer string by appending string access token - var bearer = "Bearer " + token - - // Create a new request using http - req, _ := http.NewRequest("GET", url, nil) - // add authorization header to the req - req.Header.Add("Authorization", bearer) - req.Header.Add("Accept", "application/json") - - client := &http.Client{} - response, err := client.Do(req) - - // Rate Limt Retry After Needed due to only 10 requests per second allowed by API - //https://developer.cisco.com/meraki/api-v1/rate-limit/#rate-limit - for i := 0; i < retry && response.StatusCode == 429; i++ { + event.ModuleFields.Update(metric) - retryHeader := response.Header.Get("Retry-After") - if _, err := strconv.Atoi(retryHeader); err == nil { - log.Printf("Retry Limit Paused for %s second", retryHeader) - time.ParseDuration(retryHeader + "s") + reporter.Event(event) } - response, err = client.Do(req) - } - - return response, err - } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go index 325f43d4ff8..ed8b619decf 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go @@ -38,6 +38,8 @@ func reportNetwrokApplianceVPNSiteToSite(reporter mb.ReporterV2, organizationID for network_id, networkVPNSiteToSite := range networkVPNSiteToSites { for _, device := range devices { + networkSiteToSiteMode_encountered := false + networkSiteToSiteSubnet_encountered := false if device.NetworkID == string(network_id) { metric := mapstr.M{ "device.address": device.Address, @@ -59,20 +61,27 @@ func reportNetwrokApplianceVPNSiteToSite(reporter mb.ReporterV2, organizationID if networkVPNSiteToSite.Mode != "none" { if networkVPNSiteToSite.Hubs != nil { - for k, hub := range *networkVPNSiteToSite.Hubs { - metric[fmt.Sprintf("network.appliance.vpn.site_to_site.hub.%d.hub_id", k)] = hub.HubID - metric[fmt.Sprintf("network.appliance.vpn.site_to_site.hub.%d.use_default_route", k)] = *hub.UseDefaultRoute + for _, hub := range *networkVPNSiteToSite.Hubs { + metric["network.appliance.vpn.site_to_site.hub.%d.hub_id"] = hub.HubID + metric["network.appliance.vpn.site_to_site.hub.%d.use_default_route"] = *hub.UseDefaultRoute } + networkSiteToSiteMode_encountered = true + metrics = append(metrics, metric) } if networkVPNSiteToSite.Subnets != nil { - for k, subnet := range *networkVPNSiteToSite.Subnets { - metric[fmt.Sprintf("network.appliance.vpn.site_to_site.subnet.%d.local_subnet", k)] = subnet.LocalSubnet - metric[fmt.Sprintf("network.appliance.vpn.site_to_site.subnet.%d.use_vpn", k)] = *subnet.UseVpn + for _, subnet := range *networkVPNSiteToSite.Subnets { + metric["network.appliance.vpn.site_to_site.subnet.%d.local_subnet"] = subnet.LocalSubnet + metric["network.appliance.vpn.site_to_site.subnet.%d.use_vpn"] = *subnet.UseVpn } + networkSiteToSiteSubnet_encountered = true + metrics = append(metrics, metric) } } - metrics = append(metrics, metric) + if !networkSiteToSiteMode_encountered && !networkSiteToSiteSubnet_encountered { + metrics = append(metrics, metric) + } + } } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_status.go b/x-pack/metricbeat/module/meraki/device_health/device_status.go index 9928a596519..eb8b6184544 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_status.go @@ -39,6 +39,7 @@ func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, de deviceStatusMetrics := []mapstr.M{} for serial, device := range devices { metric := mapstr.M{ + "device.serial": device.Serial, "device.address": device.Address, "device.firmware": device.Firmware, "device.imei": device.Imei, @@ -50,7 +51,6 @@ func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, de "device.network_id": device.NetworkID, "device.notes": device.Notes, "device.product_type": device.ProductType, - "device.serial": device.Serial, "device.tags": device.Tags, } From c1f3dd1d235538997ebe95278557672fba1167fa Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Thu, 12 Sep 2024 09:00:37 -0600 Subject: [PATCH 11/13] remove %d from loop condition --- .../device_network_appliance_vpn_sitetosite.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go index ed8b619decf..031095452b1 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_appliance_vpn_sitetosite.go @@ -62,8 +62,8 @@ func reportNetwrokApplianceVPNSiteToSite(reporter mb.ReporterV2, organizationID if networkVPNSiteToSite.Mode != "none" { if networkVPNSiteToSite.Hubs != nil { for _, hub := range *networkVPNSiteToSite.Hubs { - metric["network.appliance.vpn.site_to_site.hub.%d.hub_id"] = hub.HubID - metric["network.appliance.vpn.site_to_site.hub.%d.use_default_route"] = *hub.UseDefaultRoute + metric["network.appliance.vpn.site_to_site.hub.hub_id"] = hub.HubID + metric["network.appliance.vpn.site_to_site.hub.use_default_route"] = *hub.UseDefaultRoute } networkSiteToSiteMode_encountered = true metrics = append(metrics, metric) @@ -71,8 +71,8 @@ func reportNetwrokApplianceVPNSiteToSite(reporter mb.ReporterV2, organizationID if networkVPNSiteToSite.Subnets != nil { for _, subnet := range *networkVPNSiteToSite.Subnets { - metric["network.appliance.vpn.site_to_site.subnet.%d.local_subnet"] = subnet.LocalSubnet - metric["network.appliance.vpn.site_to_site.subnet.%d.use_vpn"] = *subnet.UseVpn + metric["network.appliance.vpn.site_to_site.subnet.local_subnet"] = subnet.LocalSubnet + metric["network.appliance.vpn.site_to_site.subnet.use_vpn"] = *subnet.UseVpn } networkSiteToSiteSubnet_encountered = true metrics = append(metrics, metric) From bd41dd000e8bd6b427af7780fe9e2e0daa8bf145 Mon Sep 17 00:00:00 2001 From: Dan Hiebert Date: Thu, 12 Sep 2024 19:25:05 -0600 Subject: [PATCH 12/13] fixing review comments and loss latency --- .../device_appliance_performance_score.go | 1 - .../device_appliance_uplink_status_and_ha.go | 37 +++++--- .../meraki/device_health/device_health.go | 7 ++ ...vice_network_health_channel_utilization.go | 1 - .../meraki/device_health/device_status.go | 9 +- .../device_uplink_loss_and_latency.go | 91 +++++++++++++++++++ .../module/meraki/device_health/types.go | 1 - 7 files changed, 122 insertions(+), 25 deletions(-) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go index 67defc869a5..250725fd442 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_performance_score.go @@ -22,7 +22,6 @@ func getDevicePerformanceScores(client *meraki_api.Client, devices map[Serial]*D if score_res.StatusCode() != 204 { scores[Serial(device.Serial)] = &DevicePerformanceScore{ PerformanceScore: *score_val.PerfScore, - HttpStatusCode: 200, } } } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go index 684fcdb8c2a..2c90fe03e99 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_appliance_uplink_status_and_ha.go @@ -41,31 +41,38 @@ func reportApplianceUplinkStatuses(reporter mb.ReporterV2, organizationID string for _, item := range *uplink.Uplinks { + uplink_encountered := false + + metric["uplink.interface"] = item.Interface + metric["uplink.status"] = item.Status + metric["uplink.ip"] = item.IP + metric["uplink.gateway"] = item.Gateway + metric["uplink.public_ip"] = item.PublicIP + metric["uplink.primary_dns"] = item.PrimaryDNS + metric["uplink.secondary_dns"] = item.SecondaryDNS + metric["uplink.ip_assigned_by"] = item.IPAssignedBy + for _, lossLatencyMetric := range lossLatencyUplinks { if lossLatencyMetric.Interface == item.Interface && string(lossLatencyMetric.DeviceSerial) == device.Serial && lossLatencyMetric.NetworkID == device.NetworkID { for _, lossLatency := range lossLatencyMetric.Metrics { + + uplink_encountered = true //It seems there is bug in the client.Organizations.GetOrganizationDevicesUplinksLossAndLatency code returning differnt IP //To mitigate, I am additionally printing the ip as seperate value, IMO it is odd these do not match. // client.Appliance.GetOrganizationApplianceUplinkStatuses - metric["uplink.loss_latancy.ip"] = lossLatencyMetric.IP - metric["uplink.loss_latancy.@timestamp"] = lossLatency.Timestamp - metric["uplink.loss_latancy.loss_percent"] = lossLatency.LossPercent - metric["uplink.loss_latancy.latency_ms"] = lossLatency.LatencyMs - + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "uplink.loss_latancy.ip": lossLatencyMetric.IP, + "@timestamp": lossLatency.Timestamp, + "uplink.loss_latancy.loss_percent": lossLatency.LossPercent, + "uplink.loss_latancy.latency_ms": lossLatency.LatencyMs, + })) } } } - metrics = append(metrics, mapstr.Union(metric, mapstr.M{ - "uplink.interface": item.Interface, - "uplink.status": item.Status, - "uplink.ip": item.IP, - "uplink.gateway": item.Gateway, - "uplink.public_ip": item.PublicIP, - "uplink.primary_dns": item.PrimaryDNS, - "uplink.secondary_dns": item.SecondaryDNS, - "uplink.ip_assigned_by": item.IPAssignedBy, - })) + if !uplink_encountered { + metrics = append(metrics, metric) + } } } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_health.go b/x-pack/metricbeat/module/meraki/device_health/device_health.go index 29b470bdeaa..2ad36c8bde0 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_health.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_health.go @@ -123,6 +123,13 @@ func (m *MetricSet) Fetch(reporter mb.ReporterV2) error { } reportApplianceUplinkStatuses(reporter, org, devices, appliance_val, lossLatencyuplinks) + //Get & Report Device Uplink Status + uplinks, err := getDeviceUplinkMetrics(m.client, org, m.BaseMetricSet.Module().Config().Period) + if err != nil { + return fmt.Errorf("getDeviceUplinkMetrics() failed; %w", err) + } + reportDeviceUplinkMetrics(reporter, org, devices, uplinks) + //Get & Report Device License State cotermLicenses, perDeviceLicenses, systemsManagerLicense, err := getLicenseStates(m.client, org) if err != nil { diff --git a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go index c5c8f890fbe..feb179d5956 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_network_health_channel_utilization.go @@ -25,7 +25,6 @@ func getNetworkHealthChannelUtilization(client *meraki_api.Client, networks *mer return nil, fmt.Errorf("Networks.GetNetworkNetworkHealthChannelUtilization failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) } } else { - //fmt.Printf("\n#Added Networks.GetNetworkNetworkHealthChannelUtilization networkInfo; [%d] %s.\n###################", res.StatusCode(), res.Body()) networkHealthUtilizations = append(networkHealthUtilizations, networkHealthUtilization) } diff --git a/x-pack/metricbeat/module/meraki/device_health/device_status.go b/x-pack/metricbeat/module/meraki/device_health/device_status.go index eb8b6184544..14ef0807b58 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_status.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_status.go @@ -65,16 +65,11 @@ func reportDeviceStatusMetrics(reporter mb.ReporterV2, organizationID string, de metric["device.status.primary_dns"] = status.PrimaryDNS metric["device.status.public_ip"] = status.PublicIP metric["device.status.secondary_dns"] = status.SecondaryDNS - metric["device.status.status"] = status.Status + metric["device.status.value"] = status.Status } if score, ok := devicePerformanceScores[serial]; ok { - if score.HttpStatusCode == 204 { - metric["device.performance.http_status_code"] = score.HttpStatusCode - } else { - metric["device.performance.score"] = score.PerformanceScore - } - + metric["device.performance.score"] = score.PerformanceScore } deviceStatusMetrics = append(deviceStatusMetrics, metric) diff --git a/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go index aa6c2048089..45d00cfd158 100644 --- a/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go +++ b/x-pack/metricbeat/module/meraki/device_health/device_uplink_loss_and_latency.go @@ -8,6 +8,8 @@ import ( "fmt" "time" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/elastic-agent-libs/mapstr" meraki_api "github.com/meraki/dashboard-api-go/v3/sdk" ) @@ -58,3 +60,92 @@ func getDeviceUplinkLossLatencyMetrics(client *meraki_api.Client, organizationID return uplinks, nil } + +func getDeviceUplinkMetrics(client *meraki_api.Client, organizationID string, period time.Duration) ([]*Uplink, error) { + val, res, err := client.Organizations.GetOrganizationDevicesUplinksLossAndLatency( + organizationID, + &meraki_api.GetOrganizationDevicesUplinksLossAndLatencyQueryParams{ + Timespan: period.Seconds() + 10, // slightly longer than the fetch period to ensure we don't miss measurements due to jitter + }, + ) + + if err != nil { + return nil, fmt.Errorf("GetOrganizationDevicesUplinksLossAndLatency failed; [%d] %s. %w", res.StatusCode(), res.Body(), err) + } + + var uplinks []*Uplink + + for _, device := range *val { + uplink := &Uplink{ + DeviceSerial: Serial(device.Serial), + IP: device.IP, + Interface: device.Uplink, + } + + for _, measurement := range *device.TimeSeries { + if measurement.LossPercent != nil || measurement.LatencyMs != nil { + timestamp, err := time.Parse(time.RFC3339, measurement.Ts) + if err != nil { + return nil, fmt.Errorf("failed to parse timestamp [%s] in ResponseOrganizationsGetOrganizationDevicesUplinksLossAndLatency: %w", measurement.Ts, err) + } + + metric := UplinkMetric{Timestamp: timestamp} + if measurement.LossPercent != nil { + metric.LossPercent = measurement.LossPercent + } + if measurement.LatencyMs != nil { + metric.LatencyMs = measurement.LatencyMs + } + uplink.Metrics = append(uplink.Metrics, &metric) + } + } + + if len(uplink.Metrics) != 0 { + uplinks = append(uplinks, uplink) + } + } + + return uplinks, nil +} + +func reportDeviceUplinkMetrics(reporter mb.ReporterV2, organizationID string, devices map[Serial]*Device, uplinks []*Uplink) { + metrics := []mapstr.M{} + + for _, uplink := range uplinks { + if device, ok := devices[uplink.DeviceSerial]; ok { + metric := mapstr.M{ + "uplink.ip": uplink.IP, + "uplink.interface": uplink.Interface, + // fixme: repeated code serializing device metadata to mapstr + "device.address": device.Address, + "device.firmware": device.Firmware, + "device.imei": device.Imei, + "device.lan_ip": device.LanIP, + "device.location": device.Location, + "device.mac": device.Mac, + "device.model": device.Model, + "device.name": device.Name, + "device.network_id": device.NetworkID, + "device.notes": device.Notes, + "device.product_type": device.ProductType, + "device.serial": device.Serial, + "device.tags": device.Tags, + } + + for k, v := range device.Details { + metric[fmt.Sprintf("device.details.%s", k)] = v + } + + for _, uplinkMetric := range uplink.Metrics { + metrics = append(metrics, mapstr.Union(metric, mapstr.M{ + "@timestamp": uplinkMetric.Timestamp, + "uplink.loss_percent": uplinkMetric.LossPercent, + "uplink.latency_ms": uplinkMetric.LatencyMs, + })) + } + } else { + // missing device metadata; ignore + } + } + ReportMetricsForOrganization(reporter, organizationID, metrics) +} diff --git a/x-pack/metricbeat/module/meraki/device_health/types.go b/x-pack/metricbeat/module/meraki/device_health/types.go index 6e94ab9c391..8f37963abd2 100644 --- a/x-pack/metricbeat/module/meraki/device_health/types.go +++ b/x-pack/metricbeat/module/meraki/device_health/types.go @@ -95,5 +95,4 @@ type PerfScore struct { // DeviceStatus contains dynamic device attributes type DevicePerformanceScore struct { PerformanceScore float64 - HttpStatusCode int } From d99353f9e025d896fe9c7d459694ff92619e0248 Mon Sep 17 00:00:00 2001 From: tommyers-elastic <106530686+tommyers-elastic@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:17:10 +0100 Subject: [PATCH 13/13] update dashboard-api lib to version with MIT licensed ratelimiter --- go.mod | 19 ++++++++++--------- go.sum | 44 ++++++++++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index d180fb50d89..a4b32cfc5a9 100644 --- a/go.mod +++ b/go.mod @@ -138,15 +138,15 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.25.0 - golang.org/x/mod v0.19.0 - golang.org/x/net v0.27.0 + golang.org/x/crypto v0.27.0 + golang.org/x/mod v0.21.0 + golang.org/x/net v0.29.0 golang.org/x/oauth2 v0.22.0 golang.org/x/sync v0.8.0 - golang.org/x/sys v0.22.0 - golang.org/x/text v0.16.0 + golang.org/x/sys v0.25.0 + golang.org/x/text v0.18.0 golang.org/x/time v0.6.0 - golang.org/x/tools v0.23.0 + golang.org/x/tools v0.25.0 google.golang.org/api v0.191.0 google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf // indirect google.golang.org/grpc v1.64.1 @@ -261,6 +261,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bluekeyes/go-gitdiff v0.7.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect @@ -322,7 +323,6 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/juju/ratelimit v1.0.2 // indirect github.com/karrick/godirwalk v1.17.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/asmfmt v1.3.2 // indirect @@ -374,8 +374,9 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/ratelimit v0.3.1 // indirect golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect - golang.org/x/term v0.22.0 // indirect + golang.org/x/term v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -412,6 +413,6 @@ replace ( github.com/golang/glog => github.com/elastic/glog v1.0.1-0.20210831205241-7d8b5c89dfc4 github.com/google/gopacket => github.com/elastic/gopacket v1.1.20-0.20211202005954-d412fca7f83a github.com/insomniacslk/dhcp => github.com/elastic/dhcp v0.0.0-20200227161230-57ec251c7eb3 // indirect - github.com/meraki/dashboard-api-go/v3 => github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240910133632-85f77552ef82 + github.com/meraki/dashboard-api-go/v3 => github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240913150833-a945473a8f25 github.com/snowflakedb/gosnowflake => github.com/snowflakedb/gosnowflake v1.6.19 ) diff --git a/go.sum b/go.sum index 0161143c5c6..109a30996dc 100644 --- a/go.sum +++ b/go.sum @@ -387,6 +387,8 @@ github.com/awslabs/goformation/v7 v7.14.9/go.mod h1:7obldQ8NQ/AkMsgL5K3l4lRMDFB6 github.com/awslabs/kinesis-aggregation/go/v2 v2.0.0-20220623125934-28468a6701b5 h1:lxW5Q6K2IisyF5tlr6Ts0W4POGWQZco05MJjFmoeIHs= github.com/awslabs/kinesis-aggregation/go/v2 v2.0.0-20220623125934-28468a6701b5/go.mod h1:0Qr1uMHFmHsIYMcG4T7BJ9yrJtWadhOmpABCX69dwuc= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/immutable v0.2.1/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/tmpl v1.0.0/go.mod h1:igT620JFIi44B6awvU9IsDhR77IXWtFigTLil/RPdps= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -1048,6 +1050,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737 github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4= github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= github.com/hashicorp/consul/api v1.8.1/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= @@ -1187,8 +1191,6 @@ github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= -github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1297,8 +1299,6 @@ github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4f github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/meraki/dashboard-api-go/v3 v3.0.9 h1:l3VIHu+0Jy1GysHe2sSLxp+wVhY6EB2Ng3e8/ygVBXE= -github.com/meraki/dashboard-api-go/v3 v3.0.9/go.mod h1:ngZmCzPKto29KbttUw7ibrLc+5RHHBKihYFkWFrL1zE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -1637,8 +1637,8 @@ github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYa github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240910133632-85f77552ef82 h1:HkNKMd1VHxkKojkmXumIFCucJ81IDrW+Fb0/gjavtVA= -github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240910133632-85f77552ef82/go.mod h1:PS7t/0eK4QVh1lcUTJB/WL9B/Wp8dAHECGWV59f9IY0= +github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240913150833-a945473a8f25 h1:o24r+NDexzdlwgqI0Dglq2I/cdONYRACikcUmYmovtQ= +github.com/tommyers-elastic/dashboard-api-go/v3 v3.0.0-20240913150833-a945473a8f25/go.mod h1:COGDRzuD05ZS/zp0lDCTDFhx6kAuuNdhDjY0y2ifi5o= github.com/tsg/go-daemon v0.0.0-20200207173439-e704b93fd89b h1:X/8hkb4rQq3+QuOxpJK7gWmAXmZucF0EI1s1BfBLq6U= github.com/tsg/go-daemon v0.0.0-20200207173439-e704b93fd89b/go.mod h1:jAqhj/JBVC1PwcLTWd6rjQyGyItxxrhpiBl8LSuAGmw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -1780,6 +1780,8 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -1830,8 +1832,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1890,8 +1892,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1971,8 +1973,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2126,8 +2128,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2140,8 +2142,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2158,8 +2160,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2258,8 +2260,8 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2432,6 +2434,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/hjson/hjson-go.v3 v3.0.1/go.mod h1:X6zrTSVeImfwfZLfgQdInl9mWjqPqgH90jom9nym/lw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=