From b2c55429a5ad7858d0867bbedb2bac8deb7c92a0 Mon Sep 17 00:00:00 2001 From: Sergio Bobillier Date: Thu, 15 May 2025 16:38:02 +0200 Subject: [PATCH 1/5] [JAY-655] Add the Elasticsearch::Stats::Index class The class holds information about an Elasticsearch index. It is meant to be used by the Elasticsearch::Stats class, which for the moment is just an stub. In an upcoming commit the class will be filled with the needed logic to retrieve information about the indices in the Elasticsearch cluster. --- lib/jay_api/elasticsearch.rb | 1 + lib/jay_api/elasticsearch/stats.rb | 10 ++++++++++ lib/jay_api/elasticsearch/stats/index.rb | 19 +++++++++++++++++++ .../jay_api/elasticsearch/stats/index_spec.rb | 19 +++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 lib/jay_api/elasticsearch/stats.rb create mode 100644 lib/jay_api/elasticsearch/stats/index.rb create mode 100644 spec/jay_api/elasticsearch/stats/index_spec.rb diff --git a/lib/jay_api/elasticsearch.rb b/lib/jay_api/elasticsearch.rb index 7eac892..6d1dc8c 100644 --- a/lib/jay_api/elasticsearch.rb +++ b/lib/jay_api/elasticsearch.rb @@ -10,6 +10,7 @@ require_relative 'elasticsearch/query_results' require_relative 'elasticsearch/response' require_relative 'elasticsearch/search_after_results' +require_relative 'elasticsearch/stats' require_relative 'elasticsearch/tasks' require_relative 'elasticsearch/time' diff --git a/lib/jay_api/elasticsearch/stats.rb b/lib/jay_api/elasticsearch/stats.rb new file mode 100644 index 0000000..599f682 --- /dev/null +++ b/lib/jay_api/elasticsearch/stats.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative 'stats/index' + +module JayAPI + module Elasticsearch + # This class provides access to Elasticsearch's Cluster Statistic API. + class Stats; end + end +end diff --git a/lib/jay_api/elasticsearch/stats/index.rb b/lib/jay_api/elasticsearch/stats/index.rb new file mode 100644 index 0000000..7c0eb2b --- /dev/null +++ b/lib/jay_api/elasticsearch/stats/index.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module JayAPI + module Elasticsearch + class Stats + # Holds information about an Elasticsearch Index. + class Index + attr_reader :name + + # @param [String] name The name of the index. + # @param [Hash] data Information about the index. + def initialize(name, data) + @name = name + @data = data + end + end + end + end +end diff --git a/spec/jay_api/elasticsearch/stats/index_spec.rb b/spec/jay_api/elasticsearch/stats/index_spec.rb new file mode 100644 index 0000000..252d14a --- /dev/null +++ b/spec/jay_api/elasticsearch/stats/index_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'jay_api/elasticsearch/stats/index' + +RSpec.describe JayAPI::Elasticsearch::Stats::Index do + subject(:index) { described_class.new(name, data) } + + let(:name) { 'xyz01_integration_tests' } + let(:data) { {} } + + describe '#initialize' do + subject(:method_call) { index } + + it 'stores the given name' do + method_call + expect(index.name).to be(name) + end + end +end From 6181771f02ce7b6cb65e7e3309f32e7984331e32 Mon Sep 17 00:00:00 2001 From: Sergio Bobillier Date: Thu, 15 May 2025 17:14:28 +0200 Subject: [PATCH 2/5] [JAY-655] Add the Elasticsearch::Stats::Indices class The class holds information about the Indices Statistics returned by Elasticsearch's Stats API. The class has a little bit of logic to separate system and user-defined indices. It also relies on lazy enumerators to avoid instantiating too many objects when they aren't needed. In an upcoming commit the class will be used by the Elasticsearch::Stats class, which at the time of writing it's only a stub. --- lib/jay_api/elasticsearch/stats.rb | 1 + lib/jay_api/elasticsearch/stats/indices.rb | 64 +++++++++++++++ .../elasticsearch/stats/indices_spec.rb | 79 +++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 lib/jay_api/elasticsearch/stats/indices.rb create mode 100644 spec/jay_api/elasticsearch/stats/indices_spec.rb diff --git a/lib/jay_api/elasticsearch/stats.rb b/lib/jay_api/elasticsearch/stats.rb index 599f682..897da70 100644 --- a/lib/jay_api/elasticsearch/stats.rb +++ b/lib/jay_api/elasticsearch/stats.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'stats/index' +require_relative 'stats/indices' module JayAPI module Elasticsearch diff --git a/lib/jay_api/elasticsearch/stats/indices.rb b/lib/jay_api/elasticsearch/stats/indices.rb new file mode 100644 index 0000000..2770993 --- /dev/null +++ b/lib/jay_api/elasticsearch/stats/indices.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require_relative 'index' + +module JayAPI + module Elasticsearch + class Stats + # Provides access to the list of indices returned by Elasticsearch's + # Stats API + class Indices + # A lambda used to select / reject system indices (indices whose name + # starts with dot). + SYSTEM_SELECTOR = ->(name, _data) { name.starts_with?('.') } + + # @param [Hash{String=>Hash}] indices A +Hash+ with the information + # about the indices. Its keys are the names of the indices, its values + # hashes with information about each of the indices. + def initialize(indices) + @indices = indices + end + + # @return [Enumerator::Lazy] A lazy + # enumerator of +Index+ objects, one for each of the indexes. All + # indices (system and user-defined are included). + def all + @all ||= with_lazy_instantiation { indices } + end + + # @return [Enumerator::Lazy] A lazy + # enumerator of +Index+ objects. Includes only the system indices. + def system + @system ||= with_lazy_instantiation { indices.select(&SYSTEM_SELECTOR) } + end + + # @return [Enumerator::Lazy] A lazy + # enumerator of +Index+ objects. Includes only the user-defined + # indices. + def user + @user ||= with_lazy_instantiation { indices.reject(&SYSTEM_SELECTOR) } + end + + private + + attr_reader :indices + + # @param [Array(String, Hash)] args An array with two elements, the name + # of the index and its information. + # @return [JayAPI::Elasticsearch::Stats::Index] An +Index+ object + # representing the given index. + def build_index(args) + JayAPI::Elasticsearch::Stats::Index.new(*args) + end + + # Calls the given block and turns its return value into a lazy + # enumerator that instantiates an +Index+ object for each of the + # elements of the collection returned by block. + # @return [Enumerator::Lazy] + def with_lazy_instantiation(&block) + block.call.lazy.map(&method(:build_index)) + end + end + end + end +end diff --git a/spec/jay_api/elasticsearch/stats/indices_spec.rb b/spec/jay_api/elasticsearch/stats/indices_spec.rb new file mode 100644 index 0000000..eb793eb --- /dev/null +++ b/spec/jay_api/elasticsearch/stats/indices_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'jay_api/elasticsearch/stats/indices' + +RSpec.describe JayAPI::Elasticsearch::Stats::Indices do + subject(:indices) { described_class.new(indices_hash) } + + let(:indices_hash) do + { + 'xyz01_integration_test' => { + 'uuid' => 'OXRb8_IYTseG2epNa9Ls3g' + }, + 'xyz01_unit_tests' => { + 'uuid' => 'hxDdhi-3TFSndxhLesspFw' + }, + '.kibana_views' => { + 'uuid' => 'pr-VjrPARlG3lAoAfPqNog' + }, + 'xyz02_manual_verification' => { + 'uuid' => 'uaZ_kKQuSM-HaKH_LcI7BQ' + }, + '.backup' => { + 'uuid' => 'N7TZOstjRHu8mTwsLZuQ5w' + } + } + end + + shared_examples_for '#all' do + it 'returns an Enumerator::Lazy' do + expect(method_call).to be_a(Enumerator::Lazy) + end + + it 'includes the expected number of indices' do + expect(method_call.size).to eq(expected_indices_size) + end + + it 'includes only instances of JayAPI::Elasticsearch::Stats::Index' do + expect(method_call).to all(be_a(JayAPI::Elasticsearch::Stats::Index)) + end + + it 'includes the expected list of indices' do + # #to_a is needed here because of the lazy enumerator. + expect(method_call.map(&:name).to_a).to eq(expected_indices) + end + end + + describe '#all' do + subject(:method_call) { indices.all } + + let(:expected_indices_size) { 5 } + + let(:expected_indices) do + %w[xyz01_integration_test xyz01_unit_tests .kibana_views xyz02_manual_verification .backup] + end + + it_behaves_like '#all' + end + + describe '#system' do + subject(:method_call) { indices.system } + + let(:expected_indices_size) { 2 } + let(:expected_indices) { %w[.kibana_views .backup] } + + it_behaves_like '#all' + end + + describe '#user' do + subject(:method_call) { indices.user } + + let(:expected_indices_size) { 3 } + + let(:expected_indices) do + %w[xyz01_integration_test xyz01_unit_tests xyz02_manual_verification] + end + + it_behaves_like '#all' + end +end From 96d3659a5e888722c0b81c4234cb4edc434d6feb Mon Sep 17 00:00:00 2001 From: Sergio Bobillier Date: Thu, 15 May 2025 18:15:34 +0200 Subject: [PATCH 3/5] [JAY-655] Implement the Elasticsearch::Stats class Implement the logic inside the class. The class gives the user access to Elasticsearch's Statistics API. For the moment only one method is available: #indices, which returns an object with information about the indices available on the cluster. --- lib/jay_api/elasticsearch/stats.rb | 30 +++++- spec/jay_api/elasticsearch/stats_spec.rb | 120 +++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 spec/jay_api/elasticsearch/stats_spec.rb diff --git a/lib/jay_api/elasticsearch/stats.rb b/lib/jay_api/elasticsearch/stats.rb index 897da70..be978d4 100644 --- a/lib/jay_api/elasticsearch/stats.rb +++ b/lib/jay_api/elasticsearch/stats.rb @@ -6,6 +6,34 @@ module JayAPI module Elasticsearch # This class provides access to Elasticsearch's Cluster Statistic API. - class Stats; end + class Stats + attr_reader :transport_client, :logger + + # @param [Elasticsearch::Transport::Client] transport_client The transport + # client to use to make requests to the cluster. + def initialize(transport_client) + @transport_client = transport_client + end + + # @return [JayAPI::Elasticsearch::Stats::Indices] Information about the + # indices that exist in the Elasticsearch cluster. + # @raise [Elasticsearch::Transport::Transport::ServerError] If the request + # to the Statistics API endpoint fails. + def indices + # DO NOT MEMOIZE! Leave it to the caller. + ::JayAPI::Elasticsearch::Stats::Indices.new(response['indices']) + end + + private + + # @return [Hash] The Hash with the statistics returned by the + # Elasticsearch cluster. + # @raise [Elasticsearch::Transport::Transport::ServerError] If the + # request fails. + def response + # DO NOT MEMOIZE! Leave it to the caller. + transport_client.indices.stats + end + end end end diff --git a/spec/jay_api/elasticsearch/stats_spec.rb b/spec/jay_api/elasticsearch/stats_spec.rb new file mode 100644 index 0000000..742b44f --- /dev/null +++ b/spec/jay_api/elasticsearch/stats_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'elasticsearch/transport/transport/errors' +require 'jay_api/elasticsearch/stats' + +RSpec.describe JayAPI::Elasticsearch::Stats do + subject(:stats) { described_class.new(transport_client) } + + let(:stats_hash) do + { + '_all' => { + 'total' => { + 'store' => { + 'size_in_bytes' => 830_124_136, + 'reserved_in_bytes' => 0 + } + } + }, + 'indices' => { + 'xyz01_integration_test' => { + 'uuid' => 'OXRb8_IYTseG2epNa9Ls3g' + }, + 'xyz01_unit_tests' => { + 'uuid' => 'hxDdhi-3TFSndxhLesspFw' + }, + '.kibana_views' => { + 'uuid' => 'pr-VjrPARlG3lAoAfPqNog' + }, + 'xyz02_manual_verification' => { + 'uuid' => 'uaZ_kKQuSM-HaKH_LcI7BQ' + }, + '.backup' => { + 'uuid' => 'N7TZOstjRHu8mTwsLZuQ5w' + } + } + } + end + + let(:indices_client) do + instance_double( + Elasticsearch::API::Indices::IndicesClient, + stats: stats_hash + ) + end + + let(:transport_client) do + instance_double( + Elasticsearch::Transport::Client, + indices: indices_client + ) + end + + describe '#indices' do + subject(:method_call) { stats.indices } + + let(:indices) do + instance_double( + JayAPI::Elasticsearch::Stats::Indices + ) + end + + let(:expected_indices_data) do + { + 'xyz01_integration_test' => { + 'uuid' => 'OXRb8_IYTseG2epNa9Ls3g' + }, + 'xyz01_unit_tests' => { + 'uuid' => 'hxDdhi-3TFSndxhLesspFw' + }, + '.kibana_views' => { + 'uuid' => 'pr-VjrPARlG3lAoAfPqNog' + }, + 'xyz02_manual_verification' => { + 'uuid' => 'uaZ_kKQuSM-HaKH_LcI7BQ' + }, + '.backup' => { + 'uuid' => 'N7TZOstjRHu8mTwsLZuQ5w' + } + } + end + + before do + allow(JayAPI::Elasticsearch::Stats::Indices).to receive(:new).and_return(indices) + end + + it "requests the indices' statistics from the Elasticsearch cluster" do + expect(transport_client).to receive(:indices) + expect(indices_client).to receive(:stats) + method_call + end + + context 'when the API request fails' do + let(:error) do + [ + Elasticsearch::Transport::Transport::Errors::Unauthorized, + 'Authentication failed' + ] + end + + before do + allow(indices_client).to receive(:stats).and_raise(*error) + end + + it 're-raises the error' do + expect { method_call }.to raise_error(*error) + end + end + + it 'creates an instance of JayAPI::Elasticsearch::Stats::Indices and passes the indices data to it' do + expect(JayAPI::Elasticsearch::Stats::Indices) + .to receive(:new).with(expected_indices_data) + + method_call + end + + it 'returns the instance of JayAPI::Elasticsearch::Stats::Indices' do + expect(method_call).to be(indices) + end + end +end From f6f77e92fc7ebdfc5b7f669ecedb351600b9b2c4 Mon Sep 17 00:00:00 2001 From: Sergio Bobillier Date: Thu, 15 May 2025 18:20:09 +0200 Subject: [PATCH 4/5] [JAY-655] Add Elasticsearch::Client#stats The method returns an object with statistics about the Elasticsearch cluster. --- CHANGELOG.md | 5 ++++ lib/jay_api/elasticsearch/client.rb | 7 ++++++ .../jay_api/elasticsearch/client_spec.rb | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b20ae..0674e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Please mark backwards incompatible changes with an exclamation mark at the start ## [Unreleased] +### Added +- The `#stats` method to the `Elasticsearch::Client` class. The method returns + an object that can be used to retrieve statistics about the Cluster. For the + moment only `#indices` is available, which returns index-related statistics. + ## [28.0.0] - 2025-05-09 ### Added diff --git a/lib/jay_api/elasticsearch/client.rb b/lib/jay_api/elasticsearch/client.rb index 58adfb5..2507557 100644 --- a/lib/jay_api/elasticsearch/client.rb +++ b/lib/jay_api/elasticsearch/client.rb @@ -6,6 +6,7 @@ require 'forwardable' require_relative '../abstract/connection' +require_relative 'stats' module JayAPI module Elasticsearch @@ -91,6 +92,12 @@ def task_by_id(**args) retry_request { transport_client.tasks.get(**args) } end + # @return [JayAPI::Elasticsearch::Stats] An instance of the +Stats+ class, + # which gives the caller access to Elasticsearch's Statistics API. + def stats + @stats ||= ::JayAPI::Elasticsearch::Stats.new(transport_client) + end + private # @param [Proc] block The block to execute. diff --git a/spec/integration/jay_api/elasticsearch/client_spec.rb b/spec/integration/jay_api/elasticsearch/client_spec.rb index be8e741..42dd8e2 100644 --- a/spec/integration/jay_api/elasticsearch/client_spec.rb +++ b/spec/integration/jay_api/elasticsearch/client_spec.rb @@ -296,4 +296,27 @@ method_call end end + + describe '#stats' do + subject(:method_call) { client.stats } + + let(:stats) do + instance_double( + JayAPI::Elasticsearch::Stats + ) + end + + before do + allow(JayAPI::Elasticsearch::Stats).to receive(:new).and_return(stats) + end + + it 'initializes an instance of JayAPI::Elasticsearch::Stats and passes the transport client to it' do + expect(JayAPI::Elasticsearch::Stats).to receive(:new).with(transport_client) + method_call + end + + it 'returns the JayAPI::Elasticsearch::Stats instance' do + expect(method_call).to be(stats) + end + end end From b632b33d607940b8a9dd965bbeb15e76e2670f83 Mon Sep 17 00:00:00 2001 From: Sergio Bobillier Date: Fri, 16 May 2025 15:21:10 +0200 Subject: [PATCH 5/5] [JAY-655] Add documentation for Elasticsearch::Stats --- .../user_guidelines/elasticsearch/stats.rst | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 documentation/source/user_guidelines/elasticsearch/stats.rst diff --git a/documentation/source/user_guidelines/elasticsearch/stats.rst b/documentation/source/user_guidelines/elasticsearch/stats.rst new file mode 100644 index 0000000..4dfe27a --- /dev/null +++ b/documentation/source/user_guidelines/elasticsearch/stats.rst @@ -0,0 +1,47 @@ +Stats +===== + +The ``Stats`` class gives you access to various statistics about the +Elasticsearch cluster. The class can be accessed by calling the ``#stats`` +method on an instance of the ``Elasticsearch::Client`` class. For example: + +.. code-block:: ruby + + require 'jay_api/elasticsearch/client_factory' + + client_factory = JayAPI::Elasticsearch::ClientFactory.new(...) + client = client_factory.create + + stats = client.stats + +The ``Stats`` class has the following methods: + +#indices +-------- + +This method gives you access to index-related statistics. The method returns an +instance of the ``Stats::Indices`` class, which in turn allows you to access +information on each of the individual indices through these methods: + +#all +++++ + +This method returns an ``Enumerator`` whose elements are ``Stats::Index`` +objects, one for each of the indices on the cluster, including system indices. + +#system ++++++++ + +This method returns an ``Enumerator`` whose elements are ``Stats::Index`` +objects, one for each of the system indices on the cluster. + +#user ++++++ + +This method returns an ``Enumerator`` whose elements are ``Stats::Index`` +objects, one for each of the user-created indices on the cluster. + +The ``Stats::Index`` objects have the following methods: + +``#name`` + The name of the index.