diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 913484dc6158..7893c1ce65f0 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -17642,6 +17642,269 @@ alias to: elasticsearch.node.name -- +*`ccr_auto_follow_stats.number_of_failed_follow_indices`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.auto_follow.failed.follow_indices.count + +-- + +*`ccr_auto_follow_stats.number_of_failed_remote_cluster_state_requests`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.auto_follow.failed.remote_cluster_state_requests.count + +-- + +*`ccr_auto_follow_stats.number_of_successful_follow_indices`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.auto_follow.success.follow_indices.count + +-- + + +*`ccr_stats.shard_id`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.shard_id + +-- + +*`ccr_stats.remote_cluster`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.remote_cluster + +-- + +*`ccr_stats.leader_index`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.leader.index + +-- + +*`ccr_stats.follower_index`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.follower.index + +-- + +*`ccr_stats.leader_global_checkpoint`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.leader.global_checkpoint + +-- + +*`ccr_stats.leader_max_seq_no`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.leader.max_seq_no + +-- + +*`ccr_stats.follower_global_checkpoint`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.follower.global_checkpoint + +-- + +*`ccr_stats.follower_max_seq_no`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.follower.max_seq_no + +-- + +*`ccr_stats.last_requested_seq_no`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.last_requested_seq_no + +-- + +*`ccr_stats.outstanding_read_requests`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.requests.outstanding.read.count + +-- + +*`ccr_stats.outstanding_write_requests`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.requests.outstanding.write.count + +-- + +*`ccr_stats.write_buffer_operation_count`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.write_buffer.operation.count + +-- + +*`ccr_stats.write_buffer_size_in_bytes`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.write_buffer.size.bytes + +-- + +*`ccr_stats.follower_mapping_version`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.follower.mapping_version + +-- + +*`ccr_stats.follower_settings_version`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.follower.settings_version + +-- + +*`ccr_stats.follower_aliases_version`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.follower.aliases_version + +-- + +*`ccr_stats.total_read_time_millis`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.total_time.read.ms + +-- + +*`ccr_stats.total_read_remote_exec_time_millis`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.total_time.read.remote_exec.ms + +-- + +*`ccr_stats.successful_read_requests`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.requests.successful.read.count + +-- + +*`ccr_stats.failed_read_requests`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.requests.failed.read.count + +-- + +*`ccr_stats.operations_read`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.follower.operations.read.count + +-- + +*`ccr_stats.operations_written`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.follower.operations_written + +-- + +*`ccr_stats.bytes_read`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.bytes_read + +-- + +*`ccr_stats.total_write_time_millis`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.total_time.write.ms + +-- + +*`ccr_stats.successful_write_requests`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.requests.successful.write.count + +-- + +*`ccr_stats.failed_write_requests`*:: ++ +-- +type: alias + +alias to: elasticsearch.ccr.requests.failed.write.count + +-- + + *`node_stats.fs.total.available_in_bytes`*:: @@ -18270,6 +18533,141 @@ Cross-cluster replication stats +*`elasticsearch.ccr.remote_cluster`*:: ++ +-- +type: keyword + +-- + +*`elasticsearch.ccr.bytes_read`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.last_requested_seq_no`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.shard_id`*:: ++ +-- +type: integer + +-- + + +*`elasticsearch.ccr.total_time.read.ms`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.total_time.read.remote_exec.ms`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.total_time.write.ms`*:: ++ +-- +type: long + +-- + + + +*`elasticsearch.ccr.requests.successful.read.count`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.requests.successful.write.count`*:: ++ +-- +type: long + +-- + + +*`elasticsearch.ccr.requests.failed.read.count`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.requests.failed.write.count`*:: ++ +-- +type: long + +-- + + +*`elasticsearch.ccr.requests.outstanding.read.count`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.requests.outstanding.write.count`*:: ++ +-- +type: long + +-- + + +*`elasticsearch.ccr.write_buffer.size.bytes`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.write_buffer.operation.count`*:: ++ +-- +type: long + +-- + + + +*`elasticsearch.ccr.auto_follow.failed.follow_indices.count`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.auto_follow.failed.remote_cluster_state_requests.count`*:: ++ +-- +type: long + +-- + + +*`elasticsearch.ccr.auto_follow.success.follow_indices.count`*:: ++ +-- +type: long + +-- + *`elasticsearch.ccr.leader.index`*:: + @@ -18291,6 +18689,13 @@ type: long -- +*`elasticsearch.ccr.leader.global_checkpoint`*:: ++ +-- +type: long + +-- + *`elasticsearch.ccr.follower.index`*:: + @@ -18342,6 +18747,44 @@ type: long -- +*`elasticsearch.ccr.follower.max_seq_no`*:: ++ +-- +Maximum sequence number of operation on the follower shard + + +type: long + +-- + +*`elasticsearch.ccr.follower.mapping_version`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.follower.settings_version`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.follower.aliases_version`*:: ++ +-- +type: long + +-- + +*`elasticsearch.ccr.follower.operations.read.count`*:: ++ +-- +type: long + +-- + [float] === cluster.stats diff --git a/metricbeat/module/elasticsearch/_meta/fields.yml b/metricbeat/module/elasticsearch/_meta/fields.yml index 07b33ad5773d..a74704b588fd 100644 --- a/metricbeat/module/elasticsearch/_meta/fields.yml +++ b/metricbeat/module/elasticsearch/_meta/fields.yml @@ -21,6 +21,100 @@ - name: name type: alias path: elasticsearch.node.name + - name: ccr_auto_follow_stats + type: group + fields: + - name: number_of_failed_follow_indices + type: alias + path: elasticsearch.ccr.auto_follow.failed.follow_indices.count + - name: number_of_failed_remote_cluster_state_requests + type: alias + path: elasticsearch.ccr.auto_follow.failed.remote_cluster_state_requests.count + - name: number_of_successful_follow_indices + type: alias + path: elasticsearch.ccr.auto_follow.success.follow_indices.count + - name: ccr_stats + type: group + fields: + - name: shard_id + type: alias + path: elasticsearch.ccr.shard_id + - name: remote_cluster + type: alias + path: elasticsearch.ccr.remote_cluster + - name: leader_index + type: alias + path: elasticsearch.ccr.leader.index + - name: follower_index + type: alias + path: elasticsearch.ccr.follower.index + - name: leader_global_checkpoint + type: alias + path: elasticsearch.ccr.leader.global_checkpoint + - name: leader_max_seq_no + type: alias + path: elasticsearch.ccr.leader.max_seq_no + - name: follower_global_checkpoint + type: alias + path: elasticsearch.ccr.follower.global_checkpoint + - name: follower_max_seq_no + type: alias + path: elasticsearch.ccr.follower.max_seq_no + - name: last_requested_seq_no + type: alias + path: elasticsearch.ccr.last_requested_seq_no + - name: outstanding_read_requests + type: alias + path: elasticsearch.ccr.requests.outstanding.read.count + - name: outstanding_write_requests + type: alias + path: elasticsearch.ccr.requests.outstanding.write.count + - name: write_buffer_operation_count + type: alias + path: elasticsearch.ccr.write_buffer.operation.count + - name: write_buffer_size_in_bytes + type: alias + path: elasticsearch.ccr.write_buffer.size.bytes + - name: follower_mapping_version + type: alias + path: elasticsearch.ccr.follower.mapping_version + - name: follower_settings_version + type: alias + path: elasticsearch.ccr.follower.settings_version + - name: follower_aliases_version + type: alias + path: elasticsearch.ccr.follower.aliases_version + - name: total_read_time_millis + type: alias + path: elasticsearch.ccr.total_time.read.ms + - name: total_read_remote_exec_time_millis + type: alias + path: elasticsearch.ccr.total_time.read.remote_exec.ms + - name: successful_read_requests + type: alias + path: elasticsearch.ccr.requests.successful.read.count + - name: failed_read_requests + type: alias + path: elasticsearch.ccr.requests.failed.read.count + - name: operations_read + type: alias + path: elasticsearch.ccr.follower.operations.read.count + - name: operations_written + type: alias + path: elasticsearch.ccr.follower.operations_written + - name: bytes_read + type: alias + path: elasticsearch.ccr.bytes_read + - name: total_write_time_millis + type: alias + path: elasticsearch.ccr.total_time.write.ms + - name: successful_write_requests + type: alias + path: elasticsearch.ccr.requests.successful.write.count + - name: failed_write_requests + type: alias + path: elasticsearch.ccr.requests.failed.write.count + - name: node_stats type: group fields: diff --git a/metricbeat/module/elasticsearch/ccr/_meta/data.json b/metricbeat/module/elasticsearch/ccr/_meta/data.json index a33929b9195d..7c022ace612c 100644 --- a/metricbeat/module/elasticsearch/ccr/_meta/data.json +++ b/metricbeat/module/elasticsearch/ccr/_meta/data.json @@ -1,39 +1,109 @@ { "@timestamp": "2017-10-12T08:05:34.853Z", - "agent": { - "hostname": "host.example.com", - "name": "host.example.com" - }, "elasticsearch": { "ccr": { + "auto_follow": { + "failed": { + "follow_indices": { + "count": 0 + }, + "remote_cluster_state_requests": { + "count": 0 + } + }, + "success": { + "follow_indices": { + "count": 1 + } + } + }, + "bytes_read": 32768, "follower": { - "global_checkpoint": -1, - "index": "my_index_f", - "operations_written": 0, + "global_checkpoint": 768, + "index": "follower_index", + "max_seq_no": 896, + "operations": { + "read": { + "count": 896 + } + }, + "operations_written": 832, + "settings_version": 2, "shard": { "number": 0 }, "time_since_last_read": { - "ms": 42294 + "ms": 8 } }, "leader": { - "index": "my_index", - "max_seq_no": -1 + "global_checkpoint": 1024, + "index": "leader_index", + "max_seq_no": 1536 + }, + "requests": { + "failed": { + "read": { + "count": 0 + }, + "write": { + "count": 0 + } + }, + "outstanding": { + "read": { + "count": 8 + }, + "write": { + "count": 2 + } + }, + "successful": { + "read": { + "count": 32 + }, + "write": { + "count": 16 + } + } + }, + "total_time": { + "read": { + "ms": 32768, + "remote_exec": { + "ms": 16384 + } + }, + "write": { + "ms": 16384 + } + }, + "write_buffer": { + "operation": { + "count": 64 + }, + "size": { + "bytes": 1536 + } } }, "cluster": { - "id": "3LbUkLkURz--FR-YO0wLNA", - "name": "es1" + "id": "8l_zoGznQRmtoX9iSC-goA", + "name": "docker-cluster" } }, + "event": { + "dataset": "elasticsearch.ccr", + "duration": 115000, + "module": "elasticsearch" + }, "metricset": { - "host": "127.0.0.1:9200", - "module": "elasticsearch", "name": "ccr", - "rtt": 115 + "period": 10000 }, "service": { - "name": "elasticsearch" + "address": "127.0.0.1:53293", + "name": "elasticsearch", + "type": "elasticsearch" } } \ No newline at end of file diff --git a/metricbeat/module/elasticsearch/ccr/_meta/fields.yml b/metricbeat/module/elasticsearch/ccr/_meta/fields.yml index ba19090815c1..60fffe012b89 100644 --- a/metricbeat/module/elasticsearch/ccr/_meta/fields.yml +++ b/metricbeat/module/elasticsearch/ccr/_meta/fields.yml @@ -4,6 +4,74 @@ Cross-cluster replication stats release: ga fields: + - name: remote_cluster + type: keyword + - name: bytes_read + type: long + - name: last_requested_seq_no + type: long + - name: shard_id + type: integer + + - name: total_time + type: group + fields: + - name: read.ms + type: long + - name: read.remote_exec.ms + type: long + - name: write.ms + type: long + + - name: requests + type: group + fields: + - name: successful + type: group + fields: + - name: read.count + type: long + - name: write.count + type: long + - name: failed + type: group + fields: + - name: read.count + type: long + - name: write.count + type: long + - name: outstanding + type: group + fields: + - name: read.count + type: long + - name: write.count + type: long + + - name: write_buffer + type: group + fields: + - name: size.bytes + type: long + - name: operation.count + type: long + + - name: auto_follow + type: group + fields: + - name: failed + type: group + fields: + - name: follow_indices.count + type: long + - name: remote_cluster_state_requests.count + type: long + - name: success + type: group + fields: + - name: follow_indices.count + type: long + - name: leader type: group fields: @@ -15,6 +83,9 @@ type: long description: > Maximum sequence number of operation on the leader shard + - name: global_checkpoint + type: long + - name: follower type: group fields: @@ -38,3 +109,15 @@ type: long description: > Global checkpoint value on follower shard + - name: max_seq_no + type: long + description: > + Maximum sequence number of operation on the follower shard + - name: mapping_version + type: long + - name: settings_version + type: long + - name: aliases_version + type: long + - name: operations.read.count + type: long diff --git a/metricbeat/module/elasticsearch/ccr/_meta/test/root.710.json b/metricbeat/module/elasticsearch/ccr/_meta/test/root.710.json new file mode 100644 index 000000000000..e83ec9204b4b --- /dev/null +++ b/metricbeat/module/elasticsearch/ccr/_meta/test/root.710.json @@ -0,0 +1,17 @@ +{ + "name": "a14cf47ef7f2", + "cluster_name": "docker-cluster", + "cluster_uuid": "8l_zoGznQRmtoX9iSC-goA", + "version": { + "number": "7.10.0", + "build_flavor": "default", + "build_type": "docker", + "build_hash": "43884496262f71aa3f33b34ac2f2271959dbf12a", + "build_date": "2020-10-28T09:54:14.068503Z", + "build_snapshot": true, + "lucene_version": "8.7.0", + "minimum_wire_compatibility_version": "7.11.0", + "minimum_index_compatibility_version": "7.0.0" + }, + "tagline": "You Know, for Search" +} diff --git a/metricbeat/module/elasticsearch/ccr/ccr.go b/metricbeat/module/elasticsearch/ccr/ccr.go index 74f7a232281d..bf6cf9c7893c 100644 --- a/metricbeat/module/elasticsearch/ccr/ccr.go +++ b/metricbeat/module/elasticsearch/ccr/ccr.go @@ -89,20 +89,7 @@ func (m *MetricSet) Fetch(r mb.ReporterV2) error { return err } - if m.XPack { - err = eventsMappingXPack(r, m, *info, content) - if err != nil { - // Since this is an x-pack code path, we log the error but don't - // return it. Otherwise it would get reported into `metricbeat-*` - // indices. - m.Logger().Error(err) - return nil - } - } else { - return eventsMapping(r, *info, content) - } - - return nil + return eventsMapping(r, *info, content) } func (m *MetricSet) checkCCRAvailability(currentElasticsearchVersion *common.Version) (message string, err error) { diff --git a/metricbeat/module/elasticsearch/ccr/ccr_test.go b/metricbeat/module/elasticsearch/ccr/ccr_test.go index f6d94c739e41..4890637de1b7 100644 --- a/metricbeat/module/elasticsearch/ccr/ccr_test.go +++ b/metricbeat/module/elasticsearch/ccr/ccr_test.go @@ -18,9 +18,11 @@ package ccr import ( + "io/ioutil" "net/http" "net/http/httptest" "strconv" + "strings" "testing" "github.com/stretchr/testify/require" @@ -30,8 +32,7 @@ import ( mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" ) -func startESServer(esVersion, license string, ccrEnabled bool) *httptest.Server { - +func createEsMuxer(esVersion, license string, ccrEnabled bool) *http.ServeMux { nodesLocalHandler := func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"nodes": { "foobar": {}}}`)) } @@ -42,7 +43,10 @@ func startESServer(esVersion, license string, ccrEnabled bool) *httptest.Server if r.URL.Path != "/" { http.NotFound(w, r) } - w.Write([]byte(`{"version": { "number": "` + esVersion + `" } }`)) + + input, _ := ioutil.ReadFile("./_meta/test/root.710.json") + input = []byte(strings.Replace(string(input), "7.10.0", esVersion, -1)) + w.Write(input) } licenseHandler := func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{ "license": { "type": "` + license + `" } }`)) @@ -50,9 +54,6 @@ func startESServer(esVersion, license string, ccrEnabled bool) *httptest.Server xpackHandler := func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{ "features": { "ccr": { "enabled": ` + strconv.FormatBool(ccrEnabled) + `}}}`)) } - ccrStatsHandler := func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "this should never have been called", 418) - } mux := http.NewServeMux() mux.Handle("/_nodes/_local/nodes", http.HandlerFunc(nodesLocalHandler)) @@ -61,9 +62,8 @@ func startESServer(esVersion, license string, ccrEnabled bool) *httptest.Server mux.Handle("/_license", http.HandlerFunc(licenseHandler)) // for 7.0 and above mux.Handle("/_xpack/license", http.HandlerFunc(licenseHandler)) // for before 7.0 mux.Handle("/_xpack", http.HandlerFunc(xpackHandler)) - mux.Handle("/_ccr/stats", http.HandlerFunc(ccrStatsHandler)) - return httptest.NewServer(mux) + return mux } func TestCCRNotAvailable(t *testing.T) { @@ -95,7 +95,12 @@ func TestCCRNotAvailable(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - server := startESServer(test.esVersion, test.license, test.ccrEnabled) + mux := createEsMuxer(test.esVersion, test.license, test.ccrEnabled) + mux.Handle("/_ccr/stats", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "this should never have been called", 418) + })) + + server := httptest.NewServer(mux) defer server.Close() ms := mbtest.NewReportingMetricSetV2Error(t, getConfig(server.URL)) diff --git a/metricbeat/module/elasticsearch/ccr/data.go b/metricbeat/module/elasticsearch/ccr/data.go index 8d7d11bffa39..7bc876439340 100644 --- a/metricbeat/module/elasticsearch/ccr/data.go +++ b/metricbeat/module/elasticsearch/ccr/data.go @@ -33,19 +33,91 @@ import ( var ( schema = s.Schema{ "leader": s.Object{ - "index": c.Str("leader_index"), - "max_seq_no": c.Int("leader_max_seq_no"), + "index": c.Str("leader_index"), + "max_seq_no": c.Int("leader_max_seq_no"), + "global_checkpoint": c.Int("leader_global_checkpoint"), }, + "total_time": s.Object{ + "read": s.Object{ + "ms": c.Int("total_read_time_millis"), + "remote_exec": s.Object{ + "ms": c.Int("total_read_remote_exec_time_millis"), + }, + }, + + "write": s.Object{ + "ms": c.Int("total_write_time_millis"), + }, + }, + "write_buffer": s.Object{ + "size": s.Object{ + "bytes": c.Int("write_buffer_size_in_bytes"), + }, + "operation": s.Object{ + "count": c.Int("write_buffer_operation_count"), + }, + }, + "bytes_read": c.Int("bytes_read"), "follower": s.Object{ "index": c.Str("follower_index"), "shard": s.Object{ "number": c.Int("shard_id"), }, "operations_written": c.Int("operations_written"), + "operations": s.Object{ + "read": s.Object{ + "count": c.Int("operations_read"), + }, + }, + "max_seq_no": c.Int("follower_max_seq_no"), "time_since_last_read": s.Object{ "ms": c.Int("time_since_last_read_millis"), }, "global_checkpoint": c.Int("follower_global_checkpoint"), + "settings_version": c.Int("follower_settings_version"), + "aliases_version": c.Int("follower_aliases_version"), + }, + "requests": s.Object{ + "successful": s.Object{ + "read": s.Object{ + "count": c.Int("successful_read_requests"), + }, + "write": s.Object{ + "count": c.Int("successful_write_requests"), + }, + }, + "failed": s.Object{ + "read": s.Object{ + "count": c.Int("failed_read_requests"), + }, + "write": s.Object{ + "count": c.Int("failed_write_requests"), + }, + }, + "outstanding": s.Object{ + "read": s.Object{ + "count": c.Int("outstanding_read_requests"), + }, + "write": s.Object{ + "count": c.Int("outstanding_write_requests"), + }, + }, + }, + } + + autoFollowSchema = s.Schema{ + "failed": s.Object{ + "follow_indices": s.Object{ + "count": c.Int("number_of_failed_follow_indices"), + }, + "remote_cluster_state_requests": s.Object{ + "count": c.Int("number_of_failed_remote_cluster_state_requests"), + }, + }, + "success": s.Object{ + "follow_indices": s.Object{ + "count": c.Int("number_of_successful_follow_indices"), + }, }, } ) @@ -77,11 +149,10 @@ func eventsMapping(r mb.ReporterV2, info elasticsearch.Info, content []byte) err event.ModuleFields.Put("cluster.name", info.ClusterName) event.ModuleFields.Put("cluster.id", info.ClusterID) - event.MetricSetFields, err = schema.Apply(followerShard) - if err != nil { - errs = append(errs, errors.Wrap(err, "failure applying shard schema")) - continue - } + event.MetricSetFields, _ = schema.Apply(followerShard) + + autoFollow, _ := autoFollowSchema.Apply(data.AutoFollowStats) + event.MetricSetFields["auto_follow"] = autoFollow r.Event(event) } diff --git a/metricbeat/module/elasticsearch/ccr/data_test.go b/metricbeat/module/elasticsearch/ccr/data_test.go index c75bcdda5044..155d4a6fc017 100644 --- a/metricbeat/module/elasticsearch/ccr/data_test.go +++ b/metricbeat/module/elasticsearch/ccr/data_test.go @@ -21,6 +21,8 @@ package ccr import ( "io/ioutil" + "net/http" + "net/http/httptest" "testing" "github.com/stretchr/testify/require" @@ -47,3 +49,19 @@ func TestEmpty(t *testing.T) { require.Equal(t, 0, len(reporter.GetErrors())) require.Equal(t, 0, len(reporter.GetEvents())) } + +func TestData(t *testing.T) { + mux := createEsMuxer("7.6.0", "platinum", true) + mux.Handle("/_ccr/stats", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + input, _ := ioutil.ReadFile("./_meta/test/ccr_stats.700.json") + w.Write(input) + })) + + server := httptest.NewServer(mux) + defer server.Close() + + ms := mbtest.NewReportingMetricSetV2Error(t, getConfig(server.URL)) + if err := mbtest.WriteEventsReporterV2Error(ms, t, ""); err != nil { + t.Fatal("write", err) + } +} diff --git a/metricbeat/module/elasticsearch/ccr/data_xpack.go b/metricbeat/module/elasticsearch/ccr/data_xpack.go deleted file mode 100644 index 547397f18a0c..000000000000 --- a/metricbeat/module/elasticsearch/ccr/data_xpack.go +++ /dev/null @@ -1,78 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -package ccr - -import ( - "encoding/json" - "time" - - "github.com/pkg/errors" - - "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/metricbeat/helper/elastic" - "github.com/elastic/beats/v7/metricbeat/mb" - "github.com/elastic/beats/v7/metricbeat/module/elasticsearch" -) - -func eventsMappingXPack(r mb.ReporterV2, m *MetricSet, info elasticsearch.Info, content []byte) error { - var data response - err := json.Unmarshal(content, &data) - if err != nil { - return errors.Wrap(err, "failure parsing Elasticsearch CCR Stats API response") - } - - now := common.Time(time.Now()) - intervalMS := m.Module().Config().Period / time.Millisecond - index := elastic.MakeXPackMonitoringIndexName(elastic.Elasticsearch) - - indexCCRStats(r, data, info, now, intervalMS, index) - indexCCRAutoFollowStats(r, data, info, now, intervalMS, index) - return nil -} - -func indexCCRStats(r mb.ReporterV2, ccrData response, esInfo elasticsearch.Info, now common.Time, intervalMS time.Duration, indexName string) { - for _, followerIndex := range ccrData.FollowStats.Indices { - for _, followerShard := range followerIndex.Shards { - event := mb.Event{} - event.RootFields = common.MapStr{ - "cluster_uuid": esInfo.ClusterID, - "timestamp": now, - "interval_ms": intervalMS, - "type": "ccr_stats", - "ccr_stats": followerShard, - } - - event.Index = indexName - r.Event(event) - } - } -} - -func indexCCRAutoFollowStats(r mb.ReporterV2, ccrData response, esInfo elasticsearch.Info, now common.Time, intervalMS time.Duration, indexName string) { - event := mb.Event{} - event.RootFields = common.MapStr{ - "cluster_uuid": esInfo.ClusterID, - "timestamp": now, - "interval_ms": intervalMS, - "type": "ccr_auto_follow_stats", - "ccr_auto_follow_stats": ccrData.AutoFollowStats, - } - - event.Index = indexName - r.Event(event) -} diff --git a/metricbeat/module/elasticsearch/elasticsearch_integration_test.go b/metricbeat/module/elasticsearch/elasticsearch_integration_test.go index 5da263eb000d..536b83ee2b68 100644 --- a/metricbeat/module/elasticsearch/elasticsearch_integration_test.go +++ b/metricbeat/module/elasticsearch/elasticsearch_integration_test.go @@ -127,7 +127,7 @@ func TestXPackEnabled(t *testing.T) { setupTest(t, host, version) metricSetToTypesMap := map[string][]string{ - "ccr": []string{"ccr_stats", "ccr_auto_follow_stats"}, + "ccr": []string{}, // no longer indexed into .monitoring-es-* "cluster_stats": []string{"cluster_stats"}, "enrich": []string{}, // no longer indexed into .monitoring-es-* "index_recovery": []string{"index_recovery"}, diff --git a/metricbeat/module/elasticsearch/fields.go b/metricbeat/module/elasticsearch/fields.go index bbfb46a40d3a..c4541cd2859e 100644 --- a/metricbeat/module/elasticsearch/fields.go +++ b/metricbeat/module/elasticsearch/fields.go @@ -32,5 +32,5 @@ func init() { // AssetElasticsearch returns asset data. // This is the base64 encoded gzipped contents of module/elasticsearch. func AssetElasticsearch() string { - return "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" + return "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" }