New Upstream Snapshot - libsearch-elasticsearch-client-2-0-perl
Ready changes
Summary
Merged new upstream version: 6.81+git20221229.1.cd7dfdd (was: 6.81).
Resulting package
Built on 2023-01-06T15:30 (took 10m2s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-snapshots libsearch-elasticsearch-client-2-0-perl
Lintian Result
- libsearch-elasticsearch-client-2-0-perl_6.81+git20221229.1.cd7dfdd-1~jan+nus1.dsc
- libsearch-elasticsearch-client-2-0-perl_6.81+git20221229.1.cd7dfdd-1~jan+nus1_all.deb
- libsearch-elasticsearch-client-2-0-perl_6.81+git20221229.1.cd7dfdd-1~jan+nus1_amd64.buildinfo
- libsearch-elasticsearch-client-2-0-perl_6.81+git20221229.1.cd7dfdd-1~jan+nus1_amd64.changes
Diff
diff --git a/Changes b/Changes
deleted file mode 100644
index 27b1b4f..0000000
--- a/Changes
+++ /dev/null
@@ -1,21 +0,0 @@
-Revision history for Search::Elasticsearch::Client::2_0
-
-6.81 2020-06-26
- Bumped to version 6.81
-
-6.80 2020-03-25
- Bumped to version 6.80
-
-6.80_1 2020-03-11
- Bumped to version 6.80
-
-5.02 2017-04-02
- Updated to work with Search::Elasticsearch 5.02
-
-5.01 2016-10-19
-
- Doc fixes
-
-5.00 2016-10-19
-
- First release of the 2_0 client module for Search::Elasticsearch 5.00
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 9f2ce7c..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,207 +0,0 @@
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/MANIFEST b/MANIFEST
deleted file mode 100644
index 1113f1b..0000000
--- a/MANIFEST
+++ /dev/null
@@ -1,47 +0,0 @@
-# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.012.
-Changes
-LICENSE
-MANIFEST
-META.json
-Makefile.PL
-README
-lib/Search/Elasticsearch/Client/2_0.pm
-lib/Search/Elasticsearch/Client/2_0/Bulk.pm
-lib/Search/Elasticsearch/Client/2_0/Direct.pm
-lib/Search/Elasticsearch/Client/2_0/Direct/Cat.pm
-lib/Search/Elasticsearch/Client/2_0/Direct/Cluster.pm
-lib/Search/Elasticsearch/Client/2_0/Direct/Indices.pm
-lib/Search/Elasticsearch/Client/2_0/Direct/Nodes.pm
-lib/Search/Elasticsearch/Client/2_0/Direct/Snapshot.pm
-lib/Search/Elasticsearch/Client/2_0/Direct/Tasks.pm
-lib/Search/Elasticsearch/Client/2_0/Role/API.pm
-lib/Search/Elasticsearch/Client/2_0/Role/Bulk.pm
-lib/Search/Elasticsearch/Client/2_0/Role/Scroll.pm
-lib/Search/Elasticsearch/Client/2_0/Scroll.pm
-lib/Search/Elasticsearch/Client/2_0/TestServer.pm
-t/Client_2_0/00_print_version.t
-t/Client_2_0/10_live.t
-t/Client_2_0/15_conflict.t
-t/Client_2_0/20_fork_httptiny.t
-t/Client_2_0/21_fork_lwp.t
-t/Client_2_0/22_fork_hijk.t
-t/Client_2_0/30_bulk_add_action.t
-t/Client_2_0/31_bulk_helpers.t
-t/Client_2_0/32_bulk_flush.t
-t/Client_2_0/33_bulk_errors.t
-t/Client_2_0/34_bulk_cxn_errors.t
-t/Client_2_0/40_scroll.t
-t/Client_2_0/50_reindex.t
-t/Client_2_0/60_auth_httptiny.t
-t/Client_2_0/61_auth_lwp.t
-t/author-eol.t
-t/author-no-tabs.t
-t/author-pod-syntax.t
-t/lib/LogCallback.pl
-t/lib/MockCxn.pm
-t/lib/bad_cacert.pem
-t/lib/default_cxn.pl
-t/lib/es_sync.pl
-t/lib/es_sync_auth.pl
-t/lib/es_sync_fork.pl
-t/lib/index_test_data.pl
diff --git a/META.json b/META.json
index 29527aa..41531e7 100644
--- a/META.json
+++ b/META.json
@@ -1,10 +1,10 @@
{
- "abstract" : "Thin client with full support for Elasticsearch 2.x APIs",
+ "abstract" : "The official client for Elasticsearch",
"author" : [
"Enrico Zimuel <enrico.zimuel@elastic.co>"
],
"dynamic_config" : 0,
- "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010",
+ "generated_by" : "Dist::Zilla version 6.029, CPAN::Meta::Converter version 2.150010",
"license" : [
"apache_2_0"
],
@@ -12,67 +12,87 @@
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
"version" : 2
},
- "name" : "Search-Elasticsearch-Client-2_0",
+ "name" : "Search-Elasticsearch",
"prereqs" : {
- "configure" : {
- "requires" : {
- "ExtUtils::MakeMaker" : "0"
- }
- },
- "develop" : {
- "requires" : {
- "Test::EOL" : "0",
- "Test::More" : "0.88",
- "Test::NoTabs" : "0",
- "Test::Pod" : "1.41"
- }
- },
"runtime" : {
+ "recommends" : {
+ "URI::Escape::XS" : "0"
+ },
"requires" : {
+ "Any::URI::Escape" : "0",
+ "AnyEvent::HTTP" : "0",
+ "AnyEvent::TLS" : "0",
+ "Data::Dumper" : "0",
"Devel::GlobalDestruction" : "0",
- "Moo" : "0",
+ "Encode" : "0",
+ "File::Temp" : "0",
+ "HTTP::Headers" : "0",
+ "HTTP::Parser::XS" : "0",
+ "HTTP::Request" : "0",
+ "HTTP::Tiny" : "0.076",
+ "IO::Compress::Deflate" : "0",
+ "IO::Compress::Gzip" : "0",
+ "IO::Select" : "0",
+ "IO::Socket" : "0",
+ "IO::Uncompress::Gunzip" : "0",
+ "IO::Uncompress::Inflate" : "0",
+ "JSON::MaybeXS" : "1.002002",
+ "JSON::PP" : "0",
+ "LWP::UserAgent" : "0",
+ "List::Util" : "0",
+ "Log::Any" : "1.02",
+ "Log::Any::Adapter" : "0",
+ "MIME::Base64" : "0",
+ "Module::Runtime" : "0",
+ "Moo" : "2.001000",
"Moo::Role" : "0",
- "Search::Elasticsearch" : "6.00",
- "Search::Elasticsearch::Role::API" : "0",
- "Search::Elasticsearch::Role::Client::Direct" : "0",
- "Search::Elasticsearch::Role::Is_Sync" : "0",
- "Search::Elasticsearch::Util" : "0",
+ "Net::Curl::Easy" : "0",
+ "Net::IP" : "0",
+ "POSIX" : "0",
+ "Package::Stash" : "0.34",
+ "Promises" : "0.93",
+ "Scalar::Util" : "0",
+ "Sub::Exporter" : "0",
+ "Time::HiRes" : "0",
"Try::Tiny" : "0",
+ "URI" : "0",
"namespace::clean" : "0",
+ "overload" : "0",
+ "parent" : "0",
"strict" : "0",
"warnings" : "0"
}
},
"test" : {
+ "recommends" : {
+ "Cpanel::JSON::XS" : "0",
+ "JSON::XS" : "2.26",
+ "Mojo::IOLoop" : "0",
+ "Mojo::UserAgent" : "0"
+ },
"requires" : {
- "Data::Dumper" : "0",
+ "AE" : "0",
+ "EV" : "0",
"IO::Socket::SSL" : "0",
- "Log::Any::Adapter" : "0",
"Log::Any::Adapter::Callback" : "0.09",
- "POSIX" : "0",
- "Search::Elasticsearch::Role::Cxn" : "0",
- "Sub::Exporter" : "0",
+ "TAP::Harness::JUnit" : "0",
"Test::Deep" : "0",
+ "Test::EOL" : "0",
"Test::Exception" : "0",
"Test::More" : "0.98",
+ "Test::NoTabs" : "0",
+ "Test::SharedFork" : "0",
+ "YAML" : "0",
+ "YAML::XS" : "0",
+ "base" : "2.18",
"lib" : "0"
}
}
},
"release_status" : "stable",
- "resources" : {
- "bugtracker" : {
- "web" : "https://github.com/elastic/elasticsearch-perl/issues"
- },
- "homepage" : "https://metacpan.org/pod/Search::Elasticsearch",
- "repository" : {
- "type" : "git",
- "url" : "git://github.com/elastic/elasticsearch-perl.git",
- "web" : "https://github.com/elastic/elasticsearch-perl"
- }
- },
- "version" : "6.81",
- "x_generated_by_perl" : "v5.26.1",
- "x_serialization_backend" : "Cpanel::JSON::XS version 4.19"
+ "version" : "8.00",
+ "x_generated_by_perl" : "v5.36.0",
+ "x_serialization_backend" : "Cpanel::JSON::XS version 4.32",
+ "x_spdx_expression" : "Apache-2.0"
}
diff --git a/Makefile.PL b/Makefile.PL
deleted file mode 100644
index 108fb41..0000000
--- a/Makefile.PL
+++ /dev/null
@@ -1,88 +0,0 @@
-# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.012.
-use strict;
-use warnings;
-
-
-
-use ExtUtils::MakeMaker;
-
-my %WriteMakefileArgs = (
- "ABSTRACT" => "Thin client with full support for Elasticsearch 2.x APIs",
- "AUTHOR" => "Enrico Zimuel <enrico.zimuel\@elastic.co>",
- "CONFIGURE_REQUIRES" => {
- "ExtUtils::MakeMaker" => 0
- },
- "DISTNAME" => "Search-Elasticsearch-Client-2_0",
- "LICENSE" => "apache",
- "NAME" => "Search::Elasticsearch::Client::2_0",
- "PREREQ_PM" => {
- "Devel::GlobalDestruction" => 0,
- "Moo" => 0,
- "Moo::Role" => 0,
- "Search::Elasticsearch" => "6.00",
- "Search::Elasticsearch::Role::API" => 0,
- "Search::Elasticsearch::Role::Client::Direct" => 0,
- "Search::Elasticsearch::Role::Is_Sync" => 0,
- "Search::Elasticsearch::Util" => 0,
- "Try::Tiny" => 0,
- "namespace::clean" => 0,
- "strict" => 0,
- "warnings" => 0
- },
- "TEST_REQUIRES" => {
- "Data::Dumper" => 0,
- "IO::Socket::SSL" => 0,
- "Log::Any::Adapter" => 0,
- "Log::Any::Adapter::Callback" => "0.09",
- "POSIX" => 0,
- "Search::Elasticsearch::Role::Cxn" => 0,
- "Sub::Exporter" => 0,
- "Test::Deep" => 0,
- "Test::Exception" => 0,
- "Test::More" => "0.98",
- "lib" => 0
- },
- "VERSION" => "6.81",
- "test" => {
- "TESTS" => "t/*.t t/Client_2_0/*.t"
- }
-);
-
-
-my %FallbackPrereqs = (
- "Data::Dumper" => 0,
- "Devel::GlobalDestruction" => 0,
- "IO::Socket::SSL" => 0,
- "Log::Any::Adapter" => 0,
- "Log::Any::Adapter::Callback" => "0.09",
- "Moo" => 0,
- "Moo::Role" => 0,
- "POSIX" => 0,
- "Search::Elasticsearch" => "6.00",
- "Search::Elasticsearch::Role::API" => 0,
- "Search::Elasticsearch::Role::Client::Direct" => 0,
- "Search::Elasticsearch::Role::Cxn" => 0,
- "Search::Elasticsearch::Role::Is_Sync" => 0,
- "Search::Elasticsearch::Util" => 0,
- "Sub::Exporter" => 0,
- "Test::Deep" => 0,
- "Test::Exception" => 0,
- "Test::More" => "0.98",
- "Try::Tiny" => 0,
- "lib" => 0,
- "namespace::clean" => 0,
- "strict" => 0,
- "warnings" => 0
-);
-
-
-unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) {
- delete $WriteMakefileArgs{TEST_REQUIRES};
- delete $WriteMakefileArgs{BUILD_REQUIRES};
- $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs;
-}
-
-delete $WriteMakefileArgs{CONFIGURE_REQUIRES}
- unless eval { ExtUtils::MakeMaker->VERSION(6.52) };
-
-WriteMakefile(%WriteMakefileArgs);
diff --git a/README b/README
deleted file mode 100644
index 453ca21..0000000
--- a/README
+++ /dev/null
@@ -1,29 +0,0 @@
-NAME
- Search::Elasticsearch::Client::2_0 - Thin client with full support for
- Elasticsearch 2.x APIs
-
-VERSION
- version 6.81
-
-DESCRIPTION
- The Search::Elasticsearch::Client::2_0 package provides a client
- compatible with Elasticsearch 2.x. It should be used in conjunction with
- Search::Elasticsearch as follows:
-
- $e = Search::Elasticsearch->new(
- client => "2_0::Direct"
- );
-
- See Search::Elasticsearch::Client::2_0::Direct for documentation about
- how to use the client itself.
-
-AUTHOR
- Enrico Zimuel <enrico.zimuel@elastic.co>
-
-COPYRIGHT AND LICENSE
- This software is Copyright (c) 2020 by Elasticsearch BV.
-
- This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
diff --git a/cpanfile b/cpanfile
new file mode 100644
index 0000000..a216fa5
--- /dev/null
+++ b/cpanfile
@@ -0,0 +1,71 @@
+# This file is generated by Dist::Zilla::Plugin::CPANFile v6.029
+# Do not edit this file directly. To change prereqs, edit the `dist.ini` file.
+
+requires "Any::URI::Escape" => "0";
+requires "AnyEvent::HTTP" => "0";
+requires "AnyEvent::TLS" => "0";
+requires "Data::Dumper" => "0";
+requires "Devel::GlobalDestruction" => "0";
+requires "Encode" => "0";
+requires "File::Temp" => "0";
+requires "HTTP::Headers" => "0";
+requires "HTTP::Parser::XS" => "0";
+requires "HTTP::Request" => "0";
+requires "HTTP::Tiny" => "0.076";
+requires "IO::Compress::Deflate" => "0";
+requires "IO::Compress::Gzip" => "0";
+requires "IO::Select" => "0";
+requires "IO::Socket" => "0";
+requires "IO::Uncompress::Gunzip" => "0";
+requires "IO::Uncompress::Inflate" => "0";
+requires "JSON::MaybeXS" => "1.002002";
+requires "JSON::PP" => "0";
+requires "LWP::UserAgent" => "0";
+requires "List::Util" => "0";
+requires "Log::Any" => "1.02";
+requires "Log::Any::Adapter" => "0";
+requires "MIME::Base64" => "0";
+requires "Module::Runtime" => "0";
+requires "Moo" => "2.001000";
+requires "Moo::Role" => "0";
+requires "Net::Curl::Easy" => "0";
+requires "Net::IP" => "0";
+requires "POSIX" => "0";
+requires "Package::Stash" => "0.34";
+requires "Promises" => "0.93";
+requires "Scalar::Util" => "0";
+requires "Sub::Exporter" => "0";
+requires "Time::HiRes" => "0";
+requires "Try::Tiny" => "0";
+requires "URI" => "0";
+requires "namespace::clean" => "0";
+requires "overload" => "0";
+requires "parent" => "0";
+requires "strict" => "0";
+requires "warnings" => "0";
+recommends "URI::Escape::XS" => "0";
+
+on 'test' => sub {
+ requires "AE" => "0";
+ requires "EV" => "0";
+ requires "IO::Socket::SSL" => "0";
+ requires "Log::Any::Adapter::Callback" => "0.09";
+ requires "TAP::Harness::JUnit" => "0";
+ requires "Test::Deep" => "0";
+ requires "Test::EOL" => "0";
+ requires "Test::Exception" => "0";
+ requires "Test::More" => "0.98";
+ requires "Test::NoTabs" => "0";
+ requires "Test::SharedFork" => "0";
+ requires "YAML" => "0";
+ requires "YAML::XS" => "0";
+ requires "base" => "2.18";
+ requires "lib" => "0";
+};
+
+on 'test' => sub {
+ recommends "Cpanel::JSON::XS" => "0";
+ recommends "JSON::XS" => "2.26";
+ recommends "Mojo::IOLoop" => "0";
+ recommends "Mojo::UserAgent" => "0";
+};
diff --git a/debian/changelog b/debian/changelog
index 8881f8b..94aaeb8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,9 @@
-libsearch-elasticsearch-client-2-0-perl (6.81-2) UNRELEASED; urgency=medium
+libsearch-elasticsearch-client-2-0-perl (6.81+git20221229.1.cd7dfdd-1) UNRELEASED; urgency=medium
* Update standards version to 4.6.2, no changes needed.
+ * New upstream snapshot.
- -- Debian Janitor <janitor@jelmer.uk> Fri, 06 Jan 2023 11:14:56 -0000
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 06 Jan 2023 15:26:19 -0000
libsearch-elasticsearch-client-2-0-perl (6.81-1) unstable; urgency=medium
diff --git a/lib/Search/Elasticsearch.pm b/lib/Search/Elasticsearch.pm
new file mode 100644
index 0000000..aec5065
--- /dev/null
+++ b/lib/Search/Elasticsearch.pm
@@ -0,0 +1,570 @@
+# 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 Search::Elasticsearch;
+
+use Moo 2.001000 ();
+
+use Search::Elasticsearch::Util qw(parse_params load_plugin);
+use namespace::clean;
+
+our $VERSION = '8.00';
+
+my %Default_Plugins = (
+ client => [ 'Search::Elasticsearch::Client', '8_0::Direct' ],
+ cxn_factory => [ 'Search::Elasticsearch::Cxn::Factory', '' ],
+ cxn_pool => [ 'Search::Elasticsearch::CxnPool', 'Static' ],
+ logger => [ 'Search::Elasticsearch::Logger', 'LogAny' ],
+ serializer => [ 'Search::Elasticsearch::Serializer', 'JSON' ],
+ transport => [ 'Search::Elasticsearch::Transport', '' ],
+);
+
+my @Load_Order = qw(
+ serializer
+ logger
+ cxn_factory
+ cxn_pool
+ transport
+ client
+);
+
+#===================================
+sub new {
+#===================================
+ my ( $class, $params ) = parse_params(@_);
+
+ $params->{cxn} ||= 'HTTPTiny';
+ my $plugins = delete $params->{plugins} || [];
+ $plugins = [$plugins] unless ref $plugins eq 'ARRAY';
+
+ for my $name (@Load_Order) {
+ my ( $base, $default ) = @{ $Default_Plugins{$name} };
+ my $sub_class = $params->{$name} || $default;
+ my $plugin_class = load_plugin( $base, $sub_class );
+ $params->{$name} = $plugin_class->new($params);
+ }
+
+ for my $name (@$plugins) {
+ my $plugin_class
+ = load_plugin( 'Search::Elasticsearch::Plugin', $name );
+ $plugin_class->_init_plugin($params);
+ }
+
+ return $params->{client};
+}
+
+1;
+
+__END__
+
+# ABSTRACT: The official client for Elasticsearch
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch;
+
+ # Connect to localhost:9200:
+
+ my $e = Search::Elasticsearch->new();
+
+ # Round-robin between two nodes:
+
+ my $e = Search::Elasticsearch->new(
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ]
+ );
+
+ # Connect to cluster at search1:9200, sniff all nodes and round-robin between them:
+
+ my $e = Search::Elasticsearch->new(
+ nodes => 'search1:9200',
+ cxn_pool => 'Sniff'
+ );
+
+ # Index a document:
+
+ $e->index(
+ index => 'my_app',
+ type => 'blog_post',
+ id => 1,
+ body => {
+ title => 'Elasticsearch clients',
+ content => 'Interesting content...',
+ date => '2013-09-24'
+ }
+ );
+
+ # Get the document:
+
+ my $doc = $e->get(
+ index => 'my_app',
+ type => 'blog_post',
+ id => 1
+ );
+
+ # Search:
+
+ my $results = $e->search(
+ index => 'my_app',
+ body => {
+ query => {
+ match => { title => 'elasticsearch' }
+ }
+ }
+ );
+
+ # Cluster requests:
+
+ $info = $e->cluster->info;
+ $health = $e->cluster->health;
+ $node_stats = $e->cluster->node_stats;
+
+ # Index requests:
+
+ $e->indices->create(index=>'my_index');
+ $e->indices->delete(index=>'my_index');
+
+=head1 DESCRIPTION
+
+L<Search::Elasticsearch> is the official Perl client for Elasticsearch,
+supported by L<elastic.co|http://elastic.co>. Elasticsearch
+itself is a flexible and powerful open source, distributed real-time
+search and analytics engine for the cloud. You can read more about it
+on L<elastic.co|http://www.elastic.co>.
+
+=head1 PREVIOUS VERSIONS OF ELASTICSEARCH
+
+This version of the client supports the Elasticsearch 7.0 branch,
+which is not backwards compatible with earlier branches.
+
+If you need to talk to a version of Elasticsearch before 7.0.0, please
+install one of the following packages:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Client::2_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::1_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::0_90>
+
+=back
+
+=head2 Motivation
+
+=over
+
+I<The greatest deception men suffer is from their own opinions.>
+
+Leonardo da Vinci
+
+=back
+
+All of us have opinions, especially when it comes to designing APIs.
+Unfortunately, the opinions of programmers seldom coincide. The intention of
+this client, and of the officially supported clients available for other
+languages, is to provide robust support for the full native Elasticsearch API
+with as few opinions as possible: you should be able to read the
+L<Elasticsearch reference documentation|http://www.elastic.co/guide>
+and understand how to use this client, or any of the other official clients.
+
+Should you decide that you want to customize the API, then this client
+provides the basis for your code. It does the hard stuff for you,
+allowing you to build on top of it.
+
+=head2 Features
+
+This client provides:
+
+=over
+
+=item *
+
+Full support for all Elasticsearch APIs
+
+=item *
+
+HTTP backend (for an async backend using L<Promises>, see
+L<Search::Elasticsearch::Async>)
+
+=item *
+
+Robust networking support which handles load balancing, failure detection
+and failover
+
+=item *
+
+Good defaults
+
+=item *
+
+Helper utilities for more complex operations, such as
+L<bulk indexing|Search::Elasticsearch::Client::7_0::Bulk>, and
+L<scrolled searches|Search::Elasticsearch::Client::7_0::Scroll>
+
+=item *
+
+Logging support via L<Log::Any>
+
+=item *
+
+Compatibility with the official clients for Python, Ruby, PHP, and Javascript
+
+=item *
+
+Easy extensibility
+
+=back
+
+=head1 INSTALLING ELASTICSEARCH
+
+You can download the latest version of Elasticsearch from
+L<http://www.elastic.co/download>. See the
+L<installation instructions|https://www.elastic.co/guide/en/elasticsearch/reference/current/setup.html>
+for details. You will need to have a recent version of Java installed,
+preferably the Java v8 from Sun.
+
+=head1 CREATING A NEW INSTANCE
+
+The L</new()> method returns a new L<client|Search::Elasticsearch::Client::6_0::Direct>
+which can be used to run requests against the Elasticsearch cluster.
+
+ use Search::Elasticsearch;
+ my $e = Search::Elasticsearch->new( %params );
+
+The most important arguments to L</new()> are the following:
+
+=head2 C<nodes>
+
+The C<nodes> parameter tells the client which Elasticsearch nodes it should
+talk to. It can be a single node, multiples nodes or, if not
+specified, will default to C<localhost:9200>:
+
+ # default: localhost:9200
+ $e = Search::Elasticsearch->new();
+
+ # single
+ $e = Search::Elasticsearch->new( nodes => 'search_1:9200');
+
+ # multiple
+ $e = Search::Elasticsearch->new(
+ nodes => [
+ 'search_1:9200',
+ 'search_2:9200'
+ ]
+ );
+
+Each C<node> can be a URL including a scheme, host, port, path and userinfo
+(for authentication). For instance, this would be a valid node:
+
+ https://username:password@search.domain.com:443/prefix/path
+
+See L<Search::Elasticsearch::Role::Cxn/node> for more on node specification.
+
+=head2 C<cxn_pool>
+
+The L<CxnPool|Search::Elasticsearch::Role::CxnPool> modules manage connections to
+nodes in the Elasticsearch cluster. They handle the load balancing between
+nodes and failover when nodes fail. Which C<CxnPool> you should use depends on
+where your cluster is. There are three choices:
+
+=over
+
+=item * C<Static>
+
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Static' # default
+ nodes => [
+ 'search1.domain.com:9200',
+ 'search2.domain.com:9200'
+ ],
+ );
+
+The L<Static|Search::Elasticsearch::CxnPool::Static> connection pool, which is the
+default, should be used when you don't have direct access to the Elasticsearch
+cluster, eg when you are accessing the cluster through a proxy. See
+L<Search::Elasticsearch::CxnPool::Static> for more.
+
+=item * C<Sniff>
+
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Sniff',
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ],
+ );
+
+The L<Sniff|Search::Elasticsearch::CxnPool::Sniff> connection pool should be used
+when you B<do> have direct access to the Elasticsearch cluster, eg when
+your web servers and Elasticsearch servers are on the same network.
+The nodes that you specify are used to I<discover> the cluster, which is
+then I<sniffed> to find the current list of live nodes that the cluster
+knows about. See L<Search::Elasticsearch::CxnPool::Sniff>.
+
+=item * C<Static::NoPing>
+
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Static::NoPing'
+ nodes => [
+ 'proxy1.domain.com:80',
+ 'proxy2.domain.com:80'
+ ],
+ );
+
+The L<Static::NoPing|Search::Elasticsearch::CxnPool::Static::NoPing> connection
+pool should be used when your access to a remote cluster is so limited
+that you cannot ping individual nodes with a C<HEAD /> request.
+
+See L<Search::Elasticsearch::CxnPool::Static::NoPing> for more.
+
+=back
+
+=head2 C<trace_to>
+
+For debugging purposes, it is useful to be able to dump the actual HTTP
+requests which are sent to the cluster, and the response that is received.
+This can be enabled with the C<trace_to> parameter, as follows:
+
+ # To STDERR
+ $e = Search::Elasticsearch->new(
+ trace_to => 'Stderr'
+ );
+
+ # To a file
+ $e = Search::Elasticsearch->new(
+ trace_to => ['File','/path/to/filename']
+ );
+
+Logging is handled by L<Log::Any>. See L<Search::Elasticsearch::Logger::LogAny>
+for more information.
+
+=head2 Other
+
+Other arguments are explained in the respective L<module docs|/MODULES>.
+
+=head1 RUNNING REQUESTS
+
+When you create a new instance of Search::Elasticsearch, it returns a
+L<client|Search::Elasticsearch::Client::6_0::Direct> object, which can be used for
+running requests.
+
+ use Search::Elasticsearch;
+ my $e = Search::Elasticsearch->new( %params );
+
+ # create an index
+ $e->indices->create( index => 'my_index' );
+
+ # index a document
+ $e->index(
+ index => 'my_index',
+ type => 'blog_post',
+ id => 1,
+ body => {
+ title => 'Elasticsearch clients',
+ content => 'Interesting content...',
+ date => '2013-09-24'
+ }
+ );
+
+See L<Search::Elasticsearch::Client::6_0::Direct> for more details about the requests that
+can be run.
+
+=head1 MODULES
+
+Each chunk of functionality is handled by a different module,
+which can be specified in the call to L<new()> as shown in L<cxn_pool> above.
+For instance, the following will use the L<Search::Elasticsearch::CxnPool::Sniff>
+module for the connection pool.
+
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Sniff'
+ );
+
+Custom modules can be named with the appropriate prefix,
+eg C<Search::Elasticsearch::CxnPool::>, or by prefixing the full class name
+with C<+>:
+
+ $e = Search::Elasticsearch->new(
+ cxn_pool => '+My::Custom::CxnClass'
+ );
+
+The modules that you can override are specified with the following
+arguments to L</new()>:
+
+=head2 C<client>
+
+The class to use for the client functionality, which provides
+methods that can be called to execute requests, such as
+C<search()>, C<index()> or C<delete()>. The client parses the user's
+requests and passes them to the L</transport> class to be executed.
+
+The default version of the client is C<7_0::Direct>, which can
+be explicitly specified as follows:
+
+ $e = Search::Elasticsearch->new(
+ client => '7_0::Direct'
+ );
+
+=head2 C<transport>
+
+The Transport class accepts a parsed request from the L</client> class,
+fetches a L</cxn> from its L</cxn_pool> and tries to execute the request,
+retrying after failure where appropriate. See:
+
+=over
+
+=item * L<Search::Elasticsearch::Transport>
+
+=back
+
+=head2 C<cxn>
+
+The class which handles raw requests to Elasticsearch nodes.
+See:
+
+=over
+
+=item * L<Search::Elasticsearch::Cxn::HTTPTiny> (default)
+
+=item * L<Search::Elasticsearch::Cxn::LWP>
+
+=item * L<Search::Elasticsearch::Cxn::NetCurl>
+
+=back
+
+=head2 C<cxn_factory>
+
+The class which the L</cxn_pool> uses to create new L</cxn> objects.
+See:
+
+=over
+
+=item * L<Search::Elasticsearch::Cxn::Factory>
+
+=back
+
+=head2 C<cxn_pool> (2)
+
+The class to use for the L<connection pool|/cxn_pool> functionality.
+It calls the L</cxn_factory> class to create new L</cxn> objects when
+appropriate. See:
+
+=over
+
+=item * L<Search::Elasticsearch::CxnPool::Static> (default)
+
+=item * L<Search::Elasticsearch::CxnPool::Sniff>
+
+=item * L<Search::Elasticsearch::CxnPool::Static::NoPing>
+
+=back
+
+=head2 C<logger>
+
+The class to use for logging events and tracing HTTP requests/responses. See:
+
+=over
+
+=item * L<Search::Elasticsearch::Logger::LogAny>
+
+=back
+
+=head2 C<serializer>
+
+The class to use for serializing request bodies and deserializing response
+bodies. See:
+
+=over
+
+=item * L<Search::Elasticsearch::Serializer::JSON> (default)
+
+=item * L<Search::Elasticsearch::Serializer::JSON::Cpanel>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::XS>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::PP>
+
+=back
+
+=head1 BUGS
+
+This is a stable API but this implementation is new. Watch this space
+for new releases.
+
+If you have any suggestions for improvements, or find any bugs, please report
+them to L<http://github.com/elasticsearch/elasticsearch-perl/issues>.
+I will be notified, and then you'll automatically be notified of progress on
+your bug as I make changes.
+
+=head1 SUPPORT
+
+You can find documentation for this module with the perldoc command.
+
+ perldoc Search::Elasticsearch
+
+You can also look for information at:
+
+=over 4
+
+=item * GitHub
+
+L<http://github.com/elasticsearch/elasticsearch-perl>
+
+=item * CPAN Ratings
+
+L<http://cpanratings.perl.org/d/Search::Elasticsearch>
+
+
+=item * Search MetaCPAN
+
+L<https://metacpan.org/module/Search::Elasticsearch>
+
+=item * IRC
+
+The L<#elasticsearch|irc://irc.freenode.net/elasticsearch> channel on
+C<irc.freenode.net>.
+
+=item * Mailing list
+
+The main L<Elasticsearch mailing list|http://discuss.elastic.co>.
+
+=back
+
+=head1 TEST SUITE
+
+The full test suite requires a live Elasticsearch node to run, and should
+be run as :
+
+ perl Makefile.PL
+ ES=localhost:9200 make test
+
+B<TESTS RUN IN THIS WAY ARE DESTRUCTIVE! DO NOT RUN AGAINST A CLUSTER WITH
+DATA YOU WANT TO KEEP!>
+
+You can change the Cxn class which is used by setting the C<ES_CXN>
+environment variable:
+
+ ES_CXN=NetCurl ES=localhost:9200 make test
diff --git a/lib/Search/Elasticsearch/Async.pm b/lib/Search/Elasticsearch/Async.pm
new file mode 100644
index 0000000..bdd05b3
--- /dev/null
+++ b/lib/Search/Elasticsearch/Async.pm
@@ -0,0 +1,649 @@
+# 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 Search::Elasticsearch::Async;
+
+our $VERSION = '8.00';
+use Search::Elasticsearch 8.00;
+use Promises 0.93 ();
+use parent 'Search::Elasticsearch';
+
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+
+
+#===================================
+sub new {
+#===================================
+ my ( $class, $params ) = parse_params(@_);
+ my $self = $class->SUPER::new(
+ { cxn_pool => 'Async::Static',
+ transport => 'Async',
+ cxn => 'AEHTTP',
+ %$params
+ }
+ );
+ unless ( $self->bulk_helper_class ) {
+ $self->bulk_helper_class(
+ 'Client::' . $self->api_version . '::Async::Bulk' );
+ }
+ unless ( $self->scroll_helper_class ) {
+ $self->scroll_helper_class(
+ 'Client::' . $self->api_version . '::Async::Scroll' );
+ }
+ return $self;
+}
+
+1;
+
+# ABSTRACT: Async API for Elasticsearch using Promises
+
+__END__
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch::Async;
+ use Promises backend => ['AnyEvent'];
+
+ # Connect to localhost:9200:
+
+ my $e = Search::Elasticsearch::Async->new();
+
+ # Round-robin between two nodes:
+
+ my $e = Search::Elasticsearch::Async->new(
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ]
+ );
+
+ # Connect to cluster at search1:9200, sniff all nodes and round-robin between them:
+
+ my $e = Search::Elasticsearch::Async->new(
+ nodes => 'search1:9200',
+ cxn_pool => 'Async::Sniff'
+ );
+
+ # Index a document:
+
+ $e->index(
+ index => 'my_app',
+ type => 'blog_post',
+ id => 1,
+ body => {
+ title => 'Elasticsearch clients',
+ content => 'Interesting content...',
+ date => '2013-09-24'
+ }
+ )->then( sub { my $result = shift; do_something($result) } );
+
+ # Get the document:
+
+ my $doc;
+ $e->get(
+ index => 'my_app',
+ type => 'blog_post',
+ id => 1
+ )->then( sub { $doc = shift });
+
+ # Search:
+
+ my $results;
+ $e->search(
+ index => 'my_app',
+ body => {
+ query => {
+ match => { title => 'elasticsearch' }
+ }
+ }
+ )->then( sub { $results = shift });
+
+ # Cluster requests:
+
+ $e->cluster->info ->then( sub { do_something(@_) });
+ $e->cluster->health ->then( sub { do_something(@_) });
+ $e->cluster->node_stats->then( sub { do_something(@_) });
+
+ # Index requests:
+
+ $e->indices->create(index=>'my_index')->then( sub { do_something(@_) });
+ $e->indices->delete(index=>'my_index')->then( sub { do_something(@_) });
+
+=head1 DESCRIPTION
+
+L<Search::Elasticsearch::Async> is the official B<asynchronous> Perl client for
+Elasticsearch, supported by L<elastic.co|http://elastic.co>.
+Elasticsearch itself is a flexible and powerful open source, distributed real-time
+search and analytics engine for the cloud. You can read more about it
+on L<elastic.co|http://www.elastic.co>.
+
+This module uses L<Promises> to provide a sane async interface, making your
+async code look more like synchronous code. It can be used with
+L<Mojolicious> or with any of the event loops supported by L<AnyEvent>.
+
+L<Search::Elasticsearch::Async> builds on L<Search::Elasticsearch>, which
+you should see for the main documentation.
+
+=head1 PREVIOUS VERSIONS OF ELASTICSEARCH
+
+This version of the async client supports the Elasticsearch 5.0 branch,
+which is not backwards compatible with earlier branches.
+
+If you need to talk to a version of Elasticsearch before 5.0.0, please
+install one of the following packages:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Client::2_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::1_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::0_90::Async>
+
+=back
+
+=head1 USING PROMISES
+
+First, go and read L<Promises::Cookbook::GentleIntro>, which tells you
+everything you need to know about working with L<Promises>. Using them
+with L<Search::Elasticsearch::Async> is easy:
+
+=head2 Choose a Promises backend
+
+The Promises module does not use an event loop by default. You need to specify
+the one to use at the start of your application. Typically, you will be using
+the L<EV> event loop (which both AnyEvent and Mojo prefer), in which case you
+need:
+
+ use Promises backend => ['EV'];
+
+Otherwise you can specify the C<Mojo> or C<AnyEvent> backends.
+
+=head2 Instantiate the client
+
+ use Search::Elasticsearch::Async;
+ my $es = Search::Elasticsearch::Async->new( %params );
+
+See L</"CREATING A NEW INSTANCE"> for an explantion of C<%params>.
+
+=head2 Make a request
+
+ my $promise = $es->search;
+
+All requests to Elasticsearch return a L<Promise> object, which is a value
+that will be resolved later on. You can call C<then()> on the C<$promise>
+to specify a success callback and an error callback:
+
+ $promise->then(
+ sub { my $result = shift; do_something() }, # success callback
+ sub { my $error = shift; warn $error } # error callback
+ );
+
+So far, so much like L<AnyEvent/CONDITION VARIABLES>... but
+C<then()> returns another C<$promise>, which makes them chainable:
+
+ $promise->then(sub { print "Got a result"; return @_ })
+ ->then(sub { my $result = shift; something_async($result) })
+ ->then(sub { my $next_result = shift; ...etc...})
+ ->catch(sub { warn "Something failed: @_"});
+
+See L<Promises::Cookbook::GentleIntro> for a full explanation of
+what you can do with Promises.
+
+=head2 Start the event loop
+
+Async requests are run by the event loop, so no promises will be resolved
+or rejected until the event loop is started. In a fully async application,
+you would start the event loop once and just let it run until the application
+exits. For instance, here's a simple example which reads search keywords
+from STDIN, performs an async search and prints the results. This process
+is repeated until the application is interrupted with C<Ctrl-C>.:
+
+ use v5.12;
+ use AnyEvent;
+ use Search::Elasticsearch::Async;
+
+ # EV must be installed
+ use Promises (backend => ['EV'], 'deferred');
+
+ my $es = Search::Elasticsearch::Async->new;
+
+ main();
+
+ say "Starting";
+
+ # start the event loop
+ EV::run;
+
+ sub main {
+ read_input()
+ ->then( \&do_search )
+ ->then( \&print_results )
+
+ # warn if either of the above steps throws an error
+ ->catch( sub { warn "Something went wrong: @_"; } )
+
+ # regardless of success or failure, run main() again
+ ->finally( \&main );
+ }
+
+ sub read_input {
+ say "Enter search keywords:";
+
+ # We wrap AnyEvent so that it returns a promise
+ # which is resolved when we have read from STDIN
+ my $d = deferred;
+
+ my $w;
+ $w = AnyEvent->io(
+ fh => \*STDIN,
+ poll => 'r',
+ cb => sub {
+ chomp( my $input = <STDIN> );
+ undef $w;
+
+ # resolve the promise
+ $d->resolve($input);
+ }
+ );
+
+ # return a promise
+ return $d->promise;
+ }
+
+ sub do_search {
+ my $keywords = shift();
+ # returns a promise
+ $es->search(
+ index => 'myindex',
+ body => {
+ query => {
+ match => {
+ title => $keywords
+ }
+ }
+ }
+ );
+ }
+
+ sub print_results {
+ my $results = shift;
+ my $total = $results->{hits}{total};
+
+ unless ($total) {
+ say "No results found";
+ return;
+ }
+
+ say "$total results found";
+ my $i = 1;
+ for ( @{ $results->{hits}{hits} } ) {
+ say $i++ . ': ' . $_->{_source}{title};
+ }
+ }
+
+=head1 CREATING A NEW INSTANCE
+
+The L</new()> method returns a new L<client|Search::Elasticsearch::Client::6_0::Direct>
+which can be used to run requests against the Elasticsearch cluster.
+
+ use Search::Elasticsearch::Async;
+ my $e = Search::Elasticsearch::Async->new( %params );
+
+The most important arguments to L</new()> are the following:
+
+=head2 C<nodes>
+
+The C<nodes> parameter tells the client which Elasticsearch nodes it should
+talk to. It can be a single node, multiples nodes or, if not
+specified, will default to C<localhost:9200>:
+
+ # default: localhost:9200
+ $e = Search::Elasticsearch::Async->new();
+
+ # single
+ $e = Search::Elasticsearch::Async->new( nodes => 'search_1:9200');
+
+ # multiple
+ $e = Search::Elasticsearch::Async->new(
+ nodes => [
+ 'search_1:9200',
+ 'search_2:9200'
+ ]
+ );
+
+Each C<node> can be a URL including a scheme, host, port, path and userinfo
+(for authentication). For instance, this would be a valid node:
+
+ https://username:password@search.domain.com:443/prefix/path
+
+See L<Search::Elasticsearch::Role::Cxn/node> for more on node specification.
+
+=head2 C<cxn_pool>
+
+The L<CxnPool|Search::Elasticsearch::Role::CxnPool> modules manage connections to
+nodes in the Elasticsearch cluster. They handle the load balancing between
+nodes and failover when nodes fail. Which C<CxnPool> you should use depends on
+where your cluster is. There are three choices:
+
+=over
+
+=item * C<Async::Static>
+
+ $e = Search::Elasticsearch::Async->new(
+ cxn_pool => 'Async::Static' # default
+ nodes => [
+ 'search1.domain.com:9200',
+ 'search2.domain.com:9200'
+ ],
+ );
+
+The L<Async::Static|Search::Elasticsearch::CxnPool::Async::Static> connection pool,
+which is the default, should be used when you don't have direct access to the
+Elasticsearch cluster, eg when you are accessing the cluster through a proxy. See
+L<Search::Elasticsearch::CxnPool::Async::Static> for more.
+
+=item * C<Async::Sniff>
+
+ $e = Search::Elasticsearch::Async->new(
+ cxn_pool => 'Async::Sniff',
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ],
+ );
+
+The L<Async::Sniff|Search::Elasticsearch::CxnPool::Async::Sniff> connection pool should be used
+when you B<do> have direct access to the Elasticsearch cluster, eg when
+your web servers and Elasticsearch servers are on the same network.
+The nodes that you specify are used to I<discover> the cluster, which is
+then I<sniffed> to find the current list of live nodes that the cluster
+knows about. See L<Search::Elasticsearch::CxnPool::Async::Sniff>.
+
+=item * C<Async::Static::NoPing>
+
+ $e = Search::Elasticsearch::Async->new(
+ cxn_pool => 'Async::Static::NoPing'
+ nodes => [
+ 'proxy1.domain.com:80',
+ 'proxy2.domain.com:80'
+ ],
+ );
+
+The L<Async::Static::NoPing|Search::Elasticsearch::CxnPool::Async::Static::NoPing> connection
+pool should be used when your access to a remote cluster is so limited
+that you cannot ping individual nodes with a C<HEAD /> request.
+
+See L<Search::Elasticsearch::CxnPool::Async::Static::NoPing> for more.
+
+=back
+
+=head2 C<trace_to>
+
+For debugging purposes, it is useful to be able to dump the actual HTTP
+requests which are sent to the cluster, and the response that is received.
+This can be enabled with the C<trace_to> parameter, as follows:
+
+ # To STDERR
+ $e = Search::Elasticsearch::Async->new(
+ trace_to => 'Stderr'
+ );
+
+ # To a file
+ $e = Search::Elasticsearch::Async->new(
+ trace_to => ['File','/path/to/filename']
+ );
+
+Logging is handled by L<Log::Any>. See L<Search::Elasticsearch::Logger::LogAny>
+for more information.
+
+=head2 Other
+
+Other arguments are explained in the respective L<module docs|/MODULES>.
+
+=head1 RUNNING REQUESTS
+
+When you create a new instance of Search::Elasticsearch::Async, it returns a
+L<client|Search::Elasticsearch::Client::6_0::Direct> object, which can be used for
+running requests.
+
+ use Search::Elasticsearch::Async;
+ my $e = Search::Elasticsearch::Async->new( %params );
+
+ # create an index
+ $e->indices->create( index => 'my_index' )
+
+ ->then(sub {
+
+ # index a document
+ $e->index(
+ index => 'my_index',
+ type => 'blog_post',
+ id => 1,
+ body => {
+ title => 'Elasticsearch clients',
+ content => 'Interesting content...',
+ date => '2013-09-24'
+ }
+ );
+ });
+
+See L<Search::Elasticsearch::Client::6_0::Direct> for more details about the requests
+that can be run.
+
+=head1 MODULES
+
+Each chunk of functionality is handled by a different module,
+which can be specified in the call to L<new()> as shown in L<cxn_pool> above.
+For instance, the following will use the L<Search::Elasticsearch::CxnPool::Async::Sniff>
+module for the connection pool.
+
+ $e = Search::Elasticsearch::Async->new(
+ cxn_pool => 'Async::Sniff'
+ );
+
+Custom modules can be named with the appropriate prefix,
+eg C<Search::Elasticsearch::CxnPool::>, or by prefixing the full class name
+with C<+>:
+
+ $e = Search::Elasticsearch::Async->new(
+ cxn_pool => '+My::Custom::CxnClass'
+ );
+
+The modules that you can override are specified with the following
+arguments to L</new()>:
+
+=head2 C<client>
+
+The class to use for the client functionality, which provides
+methods that can be called to execute requests, such as
+C<search()>, C<index()> or C<delete()>. The client parses the user's
+requests and passes them to the L</transport> class to be executed.
+
+The default version of the client is C<6_0::Direct>, which can
+be explicitly specified as follows:
+
+ $e = Search::Elasticsearch::Async->new(
+ client => '6_0::Direct'
+ );
+
+See :
+
+=over
+
+=item * L<Search::Elasticsearch::Client::6_0::Direct> (default, for 6.0 branch)
+
+=item * L<Search::Elasticsearch::Client::5_0::Direct> (for 5.0 branch)
+
+=item * L<Search::Elasticsearch::Client::2_0::Direct> (for 2.0 branch)
+
+=item * L<Search::Elasticsearch::Client::1_0::Direct> (for 1.0 branch)
+
+=item * L<Search::Elasticsearch::Client::0_90::Direct> (for 0.90 branch)
+
+=back
+
+=head2 C<transport>
+
+The Transport class accepts a parsed request from the L</client> class,
+fetches a L</cxn> from its L</cxn_pool> and tries to execute the request,
+retrying after failure where appropriate. See:
+
+=over
+
+=item * L<Search::Elasticsearch::Async::Transport>
+
+=back
+
+=head2 C<cxn>
+
+The class which handles raw requests to Elasticsearch nodes.
+See:
+
+=over
+
+=item * L<Search::Elasticsearch::Cxn::AEHTTP> (default)
+
+=item * L<Search::Elasticsearch::Cxn::Mojo>
+
+=back
+
+=head2 C<cxn_factory>
+
+The class which the L</cxn_pool> uses to create new L</cxn> objects.
+See:
+
+=over
+
+=item * L<Search::Elasticsearch::Cxn::Factory>
+
+=back
+
+=head2 C<cxn_pool> (2)
+
+The class to use for the L<connection pool|/cxn_pool> functionality.
+It calls the L</cxn_factory> class to create new L</cxn> objects when
+appropriate. See:
+
+=over
+
+=item * L<Search::Elasticsearch::CxnPool::Async::Static> (default)
+
+=item * L<Search::Elasticsearch::CxnPool::Async::Sniff>
+
+=item * L<Search::Elasticsearch::CxnPool::Async::Static::NoPing>
+
+=back
+
+=head2 C<logger>
+
+The class to use for logging events and tracing HTTP requests/responses. See:
+
+=over
+
+=item * L<Search::Elasticsearch::Logger::LogAny>
+
+=back
+
+=head2 C<serializer>
+
+The class to use for serializing request bodies and deserializing response
+bodies. See:
+
+=over
+
+=item * L<Search::Elasticsearch::Serializer::JSON> (default)
+
+=item * L<Search::Elasticsearch::Serializer::JSON::Cpanel>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::XS>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::PP>
+
+=back
+
+=head1 HELPER MODULES
+
+L<Search::Elasticsearch::Client::6_0::Async::Bulk> and L<Search::Elasticsearch::Client::6_0::Async::Scroll>
+are helper modules which assist with bulk indexing and scrolled searching, eg:
+
+ $es->scroll_helper(
+ index => 'myindex',
+ on_result => sub { my $doc = shift; do_something($doc) }
+ )->then( sub { say "Done!" });
+
+=head1 BUGS
+
+This is a stable API but this implementation is new. Watch this space
+for new releases.
+
+If you have any suggestions for improvements, or find any bugs, please report
+them to L<http://github.com/elasticsearch/elasticsearch-perl/issues>.
+I will be notified, and then you'll automatically be notified of progress on
+your bug as I make changes.
+
+=head1 SUPPORT
+
+You can find documentation for this module with the perldoc command.
+
+ perldoc Search::Elasticsearch::Async
+
+You can also look for information at:
+
+=over 4
+
+=item * GitHub
+
+L<http://github.com/elasticsearch/elasticsearch-perl>
+
+=item * CPAN Ratings
+
+L<http://cpanratings.perl.org/d/Search::Elasticsearch::Async>
+
+
+=item * Search MetaCPAN
+
+L<https://metacpan.org/module/Search::Elasticsearch::Async>
+
+=item * IRC
+
+The L<#elasticsearch|irc://irc.freenode.net/elasticsearch> channel on
+C<irc.freenode.net>.
+
+=item * Mailing list
+
+The main L<Elasticsearch mailing list|http://discuss.elastic.co>.
+
+=back
+
+=head1 TEST SUITE
+
+The full test suite requires a live Elasticsearch node to run, and should
+be run as :
+
+ perl Makefile.PL
+ ES=localhost:9200 make test
+
+B<TESTS RUN IN THIS WAY ARE DESTRUCTIVE! DO NOT RUN AGAINST A CLUSTER WITH
+DATA YOU WANT TO KEEP!>
diff --git a/lib/Search/Elasticsearch/Async/Util.pm b/lib/Search/Elasticsearch/Async/Util.pm
new file mode 100644
index 0000000..3d8e26f
--- /dev/null
+++ b/lib/Search/Elasticsearch/Async/Util.pm
@@ -0,0 +1,35 @@
+# 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 Search::Elasticsearch::Async::Util;
+
+use Moo;
+use Scalar::Util qw(blessed);
+use Sub::Exporter -setup => { exports => ['thenable'] };
+
+#===================================
+sub thenable {
+#===================================
+ return
+ unless @_ == 1
+ && blessed $_[0]
+ && $_[0]->can('then');
+ return shift();
+}
+1;
+
+# ABSTRACT: A utility class for internal use by Elasticsearch
diff --git a/lib/Search/Elasticsearch/Client/2_0.pm b/lib/Search/Elasticsearch/Client/2_0.pm
deleted file mode 100644
index 671935a..0000000
--- a/lib/Search/Elasticsearch/Client/2_0.pm
+++ /dev/null
@@ -1,50 +0,0 @@
-package Search::Elasticsearch::Client::2_0;
-
-our $VERSION='6.81';
-use Search::Elasticsearch 6.00 ();
-
-1;
-
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0 - Thin client with full support for Elasticsearch 2.x APIs
-
-=head1 VERSION
-
-version 6.81
-
-=head1 DESCRIPTION
-
-The L<Search::Elasticsearch::Client::2_0> package provides a client
-compatible with Elasticsearch 2.x. It should be used in conjunction
-with L<Search::Elasticsearch> as follows:
-
- $e = Search::Elasticsearch->new(
- client => "2_0::Direct"
- );
-
-See L<Search::Elasticsearch::Client::2_0::Direct> for documentation
-about how to use the client itself.
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
-
-# ABSTRACT: Thin client with full support for Elasticsearch 2.x APIs
-
diff --git a/lib/Search/Elasticsearch/Client/2_0/Direct/Tasks.pm b/lib/Search/Elasticsearch/Client/2_0/Direct/Tasks.pm
deleted file mode 100644
index d366545..0000000
--- a/lib/Search/Elasticsearch/Client/2_0/Direct/Tasks.pm
+++ /dev/null
@@ -1,85 +0,0 @@
-package Search::Elasticsearch::Client::2_0::Direct::Tasks;
-$Search::Elasticsearch::Client::2_0::Direct::Tasks::VERSION = '6.81';
-use Moo;
-with 'Search::Elasticsearch::Client::2_0::Role::API';
-with 'Search::Elasticsearch::Role::Client::Direct';
-__PACKAGE__->_install_api('tasks');
-
-1;
-
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Direct::Tasks - A client for accessing the Task Management API
-
-=head1 VERSION
-
-version 6.81
-
-=head1 DESCRIPTION
-
-This module provides methods to access the Task Management API, such as listing
-tasks and cancelling tasks.
-
-It does L<Search::Elasticsearch::Role::Client::Direct>.
-
-=head1 METHODS
-
-=head2 C<list()>
-
- $response = $e->tasks->list(
- task_id => $task_id # optional
- );
-
-The C<list()> method returns all running tasks or, if a C<task_id> is specified, info
-about that task.
-
-Query string parameters:
- C<actions>,
- C<detailed>,
- C<node_id>,
- C<parent_node>,
- C<parent_task>,
- C<wait_for_completion>
-
-See the L<task management docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
-for more information.
-
-=head2 C<cancel()>
-
- $response = $e->tasks->cancel(
- task_id => $task_id # option
- );
-
-The C<cancel()> method attempts to cancel the specified C<task_id> or multiple tasks.
-
-Query string parameters:
- C<actions>,
- C<node_id>,
- C<parent_node>,
- C<parent_task>
-
-See the L<task management docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
-for more information.
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
-
-# ABSTRACT: A client for accessing the Task Management API
-
diff --git a/lib/Search/Elasticsearch/Client/2_0/Role/API.pm b/lib/Search/Elasticsearch/Client/2_0/Role/API.pm
deleted file mode 100644
index 8de0671..0000000
--- a/lib/Search/Elasticsearch/Client/2_0/Role/API.pm
+++ /dev/null
@@ -1,2254 +0,0 @@
-package Search::Elasticsearch::Client::2_0::Role::API;
-$Search::Elasticsearch::Client::2_0::Role::API::VERSION = '6.81';
-use Moo::Role;
-with 'Search::Elasticsearch::Role::API';
-
-use Search::Elasticsearch::Util qw(throw);
-use namespace::clean;
-
-has 'api_version' => ( is => 'ro', default => '2_0' );
-
-our %API;
-
-#===================================
-sub api {
-#===================================
- my $name = $_[1] || return \%API;
- return $API{$name}
- || throw( 'Internal', "Unknown api name ($name)" );
-}
-
-#===================================
-%API = (
-#===================================
-
- 'bulk.metadata' => {
- params => [
- 'index', 'type', 'id', 'fields',
- 'routing', 'parent', 'timestamp', 'ttl',
- 'version', 'version_type'
- ]
- },
- 'bulk.update' => {
- params => [
- 'doc', 'upsert',
- 'doc_as_upsert', 'fields',
- 'scripted_upsert', 'script',
- 'script_id', 'script_file',
- 'params', 'lang',
- 'detect_noop', '_retry_on_conflict',
- ]
- },
- 'bulk.required' => { params => [ 'index', 'type' ] },
-
-#=== AUTOGEN - START ===
-
- 'bulk' => {
- body => { required => 1 },
- doc => "docs-bulk",
- method => "POST",
- parts => { index => {}, type => {} },
- paths => [
- [ { index => 0, type => 1 }, "{index}", "{type}", "_bulk" ],
- [ { index => 0 }, "{index}", "_bulk" ],
- [ {}, "_bulk" ],
- ],
- qs => {
- consistency => "enum",
- fields => "list",
- filter_path => "list",
- refresh => "boolean",
- routing => "string",
- timeout => "time",
- },
- serialize => "bulk",
- },
-
- 'clear_scroll' => {
- body => {},
- doc => "search-request-scroll",
- method => "DELETE",
- parts => { scroll_id => { multi => 1 } },
- paths => [
- [ { scroll_id => 2 }, "_search", "scroll", "{scroll_id}" ],
- [ {}, "_search", "scroll" ],
- ],
- qs => { filter_path => "list" },
- },
-
- 'count' => {
- body => {},
- doc => "search-count",
- method => "POST",
- parts => { index => { multi => 1 }, type => { multi => 1 } },
- paths => [
- [ { index => 0, type => 1 }, "{index}", "{type}", "_count" ],
- [ { index => 0 }, "{index}", "_count" ],
- [ {}, "_count" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- analyze_wildcard => "boolean",
- analyzer => "string",
- default_operator => "enum",
- df => "string",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- lenient => "boolean",
- lowercase_expanded_terms => "boolean",
- min_score => "number",
- preference => "string",
- q => "string",
- routing => "string",
- },
- },
-
- 'count_percolate' => {
- body => {},
- doc => "search-percolate",
- parts => {
- id => {},
- index => { required => 1 },
- type => { required => 1 }
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}",
- "_percolate", "count",
- ],
- [ { index => 0, type => 1 }, "{index}",
- "{type}", "_percolate",
- "count",
- ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- percolate_index => "string",
- percolate_type => "string",
- preference => "string",
- routing => "list",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'delete' => {
- doc => "docs-delete",
- method => "DELETE",
- parts => {
- id => { required => 1 },
- index => { required => 1 },
- type => { required => 1 },
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}"
- ],
- ],
- qs => {
- consistency => "enum",
- filter_path => "list",
- parent => "string",
- refresh => "boolean",
- routing => "string",
- timeout => "time",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'delete_by_query' => {
- body => {},
- doc => "plugins-delete-by-query",
- method => "DELETE",
- parts => {
- index => { multi => 1, required => 1 },
- type => { multi => 1 }
- },
- paths => [
- [ { index => 0, type => 1 }, "{index}", "{type}", "_query" ],
- [ { index => 0 }, "{index}", "_query" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- analyzer => "string",
- default_operator => "enum",
- df => "string",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- q => "string",
- routing => "string",
- timeout => "time",
- },
- },
-
- 'delete_script' => {
- doc => "modules-scripting",
- method => "DELETE",
- parts => { id => { required => 1 }, lang => { required => 1 } },
- paths => [ [ { id => 2, lang => 1 }, "_scripts", "{lang}", "{id}" ] ],
- qs => {
- filter_path => "list",
- version => "number",
- version_type => "enum"
- },
- },
-
- 'delete_template' => {
- doc => "search-template",
- method => "DELETE",
- parts => { id => { required => 1 } },
- paths => [ [ { id => 2 }, "_search", "template", "{id}" ] ],
- qs => {
- filter_path => "list",
- version => "number",
- version_type => "enum"
- },
- },
-
- 'exists' => {
- doc => "docs-get",
- method => "HEAD",
- parts => {
- id => { required => 1 },
- index => { required => 1 },
- type => { required => 1 },
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}"
- ],
- ],
- qs => {
- parent => "string",
- preference => "string",
- realtime => "boolean",
- refresh => "boolean",
- routing => "string",
- },
- },
-
- 'explain' => {
- body => {},
- doc => "search-explain",
- parts => {
- id => { required => 1 },
- index => { required => 1 },
- type => { required => 1 },
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}",
- "_explain",
- ],
- ],
- qs => {
- _source => "list",
- _source_exclude => "list",
- _source_include => "list",
- analyze_wildcard => "boolean",
- analyzer => "string",
- default_operator => "enum",
- df => "string",
- fields => "list",
- filter_path => "list",
- lenient => "boolean",
- lowercase_expanded_terms => "boolean",
- parent => "string",
- preference => "string",
- q => "string",
- routing => "string",
- },
- },
-
- 'field_stats' => {
- body => {},
- doc => "search-field-stats",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_field_stats" ],
- [ {}, "_field_stats" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- fields => "list",
- filter_path => "list",
- ignore_unavailable => "boolean",
- level => "enum",
- },
- },
-
- 'get' => {
- doc => "docs-get",
- parts => {
- id => { required => 1 },
- index => { required => 1 },
- type => { required => 1 },
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}"
- ],
- ],
- qs => {
- _source => "list",
- _source_exclude => "list",
- _source_include => "list",
- fields => "list",
- filter_path => "list",
- parent => "string",
- preference => "string",
- realtime => "boolean",
- refresh => "boolean",
- routing => "string",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'get_script' => {
- doc => "modules-scripting",
- parts => { id => { required => 1 }, lang => { required => 1 } },
- paths => [ [ { id => 2, lang => 1 }, "_scripts", "{lang}", "{id}" ] ],
- qs => {
- filter_path => "list",
- version => "number",
- version_type => "enum"
- },
- },
-
- 'get_source' => {
- doc => "docs-get",
- parts => {
- id => { required => 1 },
- index => { required => 1 },
- type => { required => 1 },
- },
- paths => [
- [ { id => 2, index => 0, type => 1 },
- "{index}", "{type}", "{id}", "_source",
- ],
- ],
- qs => {
- _source => "list",
- _source_exclude => "list",
- _source_include => "list",
- filter_path => "list",
- parent => "string",
- preference => "string",
- realtime => "boolean",
- refresh => "boolean",
- routing => "string",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'get_template' => {
- doc => "search-template",
- parts => { id => { required => 1 } },
- paths => [ [ { id => 2 }, "_search", "template", "{id}" ] ],
- qs => {
- filter_path => "list",
- version => "number",
- version_type => "enum"
- },
- },
-
- 'index' => {
- body => { required => 1 },
- doc => "docs-index_",
- method => "POST",
- parts => {
- id => {},
- index => { required => 1 },
- type => { required => 1 }
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}"
- ],
- [ { index => 0, type => 1 }, "{index}", "{type}" ],
- ],
- qs => {
- consistency => "enum",
- filter_path => "list",
- op_type => "enum",
- parent => "string",
- refresh => "boolean",
- routing => "string",
- timeout => "time",
- timestamp => "time",
- ttl => "time",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'info' => {
- doc => "",
- parts => {},
- paths => [ [ {} ] ],
- qs => { filter_path => "list" }
- },
-
- 'mget' => {
- body => { required => 1 },
- doc => "docs-multi-get",
- parts => { index => {}, type => {} },
- paths => [
- [ { index => 0, type => 1 }, "{index}", "{type}", "_mget" ],
- [ { index => 0 }, "{index}", "_mget" ],
- [ {}, "_mget" ],
- ],
- qs => {
- _source => "list",
- _source_exclude => "list",
- _source_include => "list",
- fields => "list",
- filter_path => "list",
- preference => "string",
- realtime => "boolean",
- refresh => "boolean",
- },
- },
-
- 'mpercolate' => {
- body => { required => 1 },
- doc => "search-percolate",
- parts => { index => {}, type => {} },
- paths => [
- [ { index => 0, type => 1 }, "{index}", "{type}", "_mpercolate" ],
- [ { index => 0 }, "{index}", "_mpercolate" ],
- [ {}, "_mpercolate" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- },
- serialize => "bulk",
- },
-
- 'msearch' => {
- body => { required => 1 },
- doc => "search-multi-search",
- parts => { index => { multi => 1 }, type => { multi => 1 } },
- paths => [
- [ { index => 0, type => 1 }, "{index}", "{type}", "_msearch" ],
- [ { index => 0 }, "{index}", "_msearch" ],
- [ {}, "_msearch" ],
- ],
- qs => { filter_path => "list", search_type => "enum" },
- serialize => "bulk",
- },
-
- 'mtermvectors' => {
- body => {},
- doc => "docs-multi-termvectors",
- parts => { index => {}, type => {} },
- paths => [
- [ { index => 0, type => 1 }, "{index}",
- "{type}", "_mtermvectors"
- ],
- [ { index => 0 }, "{index}", "_mtermvectors" ],
- [ {}, "_mtermvectors" ],
- ],
- qs => {
- field_statistics => "boolean",
- fields => "list",
- filter_path => "list",
- ids => "list",
- offsets => "boolean",
- parent => "string",
- payloads => "boolean",
- positions => "boolean",
- preference => "string",
- realtime => "boolean",
- routing => "string",
- term_statistics => "boolean",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'percolate' => {
- body => {},
- doc => "search-percolate",
- parts => {
- id => {},
- index => { required => 1 },
- type => { required => 1 }
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}",
- "_percolate",
- ],
- [ { index => 0, type => 1 }, "{index}", "{type}", "_percolate" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- percolate_format => "enum",
- percolate_index => "string",
- percolate_preference => "string",
- percolate_routing => "string",
- percolate_type => "string",
- preference => "string",
- routing => "list",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'ping' => {
- doc => "",
- method => "HEAD",
- parts => {},
- paths => [ [ {} ] ],
- qs => {}
- },
-
- 'put_script' => {
- body => { required => 1 },
- doc => "modules-scripting",
- method => "PUT",
- parts => { id => { required => 1 }, lang => { required => 1 } },
- paths => [ [ { id => 2, lang => 1 }, "_scripts", "{lang}", "{id}" ] ],
- qs => {
- filter_path => "list",
- op_type => "enum",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'put_template' => {
- body => { required => 1 },
- doc => "search-template",
- method => "PUT",
- parts => { id => { required => 1 } },
- paths => [ [ { id => 2 }, "_search", "template", "{id}" ] ],
- qs => {
- filter_path => "list",
- op_type => "enum",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'reindex' => {
- body => { required => 1 },
- doc => "plugins-reindex",
- method => "POST",
- parts => {},
- paths => [ [ {}, "_reindex" ] ],
- qs => {
- consistency => "enum",
- filter_path => "list",
- refresh => "boolean",
- requests_per_second => "number",
- timeout => "time",
- wait_for_completion => "boolean",
- },
- },
-
- 'reindex_rethrottle' => {
- doc => "docs-reindex",
- method => "POST",
- parts => { task_id => {} },
- paths =>
- [ [ { task_id => 1 }, "_reindex", "{task_id}", "_rethrottle" ] ],
- qs => { filter_path => "list", requests_per_second => "number" },
- },
-
- 'render_search_template' => {
- body => {},
- doc => "search-template",
- parts => { id => {} },
- paths => [
- [ { id => 2 }, "_render", "template", "{id}" ],
- [ {}, "_render", "template" ],
- ],
- qs => { filter_path => "list" },
- },
-
- 'scroll' => {
- body => {},
- doc => "search-request-scroll",
- parts => { scroll_id => {} },
- paths => [
- [ { scroll_id => 2 }, "_search", "scroll", "{scroll_id}" ],
- [ {}, "_search", "scroll" ],
- ],
- qs => { filter_path => "list", scroll => "time" },
- },
-
- 'search' => {
- body => {},
- doc => "search-search",
- parts => { index => { multi => 1 }, type => { multi => 1 } },
- paths => [
- [ { index => 0, type => 1 }, "{index}", "{type}", "_search" ],
- [ { index => 0 }, "{index}", "_search" ],
- [ {}, "_search" ],
- ],
- qs => {
- _source => "list",
- _source_exclude => "list",
- _source_include => "list",
- allow_no_indices => "boolean",
- analyze_wildcard => "boolean",
- analyzer => "string",
- default_operator => "enum",
- df => "string",
- expand_wildcards => "enum",
- explain => "boolean",
- fielddata_fields => "list",
- fields => "list",
- filter_path => "list",
- from => "number",
- ignore_unavailable => "boolean",
- lenient => "boolean",
- lowercase_expanded_terms => "boolean",
- preference => "string",
- q => "string",
- request_cache => "boolean",
- routing => "list",
- scroll => "time",
- search_type => "enum",
- size => "number",
- sort => "list",
- stats => "list",
- suggest_field => "string",
- suggest_mode => "enum",
- suggest_size => "number",
- suggest_text => "string",
- terminate_after => "number",
- timeout => "time",
- track_scores => "boolean",
- version => "boolean",
- },
- },
-
- 'search_exists' => {
- body => {},
- doc => "search-exists",
- method => "POST",
- parts => { index => { multi => 1 }, type => { multi => 1 } },
- paths => [
- [ { index => 0, type => 1 }, "{index}",
- "{type}", "_search",
- "exists",
- ],
- [ { index => 0 }, "{index}", "_search", "exists" ],
- [ {}, "_search", "exists" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- analyze_wildcard => "boolean",
- analyzer => "string",
- default_operator => "enum",
- df => "string",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- lenient => "boolean",
- lowercase_expanded_terms => "boolean",
- min_score => "number",
- preference => "string",
- q => "string",
- routing => "string",
- },
- },
-
- 'search_shards' => {
- doc => "search-shards",
- parts => { index => { multi => 1 }, type => { multi => 1 } },
- paths => [
- [ { index => 0, type => 1 }, "{index}",
- "{type}", "_search_shards",
- ],
- [ { index => 0 }, "{index}", "_search_shards" ],
- [ {}, "_search_shards" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- local => "boolean",
- preference => "string",
- routing => "string",
- },
- },
-
- 'search_template' => {
- body => {},
- doc => "search-template",
- parts => { index => { multi => 1 }, type => { multi => 1 } },
- paths => [
- [ { index => 0, type => 1 }, "{index}",
- "{type}", "_search",
- "template",
- ],
- [ { index => 0 }, "{index}", "_search", "template" ],
- [ {}, "_search", "template" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- preference => "string",
- routing => "list",
- scroll => "time",
- search_type => "enum",
- },
- },
-
- 'suggest' => {
- body => { required => 1 },
- doc => "search-suggesters",
- method => "POST",
- parts => { index => { multi => 1 } },
- paths =>
- [ [ { index => 0 }, "{index}", "_suggest" ], [ {}, "_suggest" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- preference => "string",
- routing => "string",
- },
- },
-
- 'termvectors' => {
- body => {},
- doc => "docs-termvectors",
- parts => {
- id => {},
- index => { required => 1 },
- type => { required => 1 }
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}",
- "_termvectors",
- ],
- [ { index => 0, type => 1 }, "{index}", "{type}",
- "_termvectors"
- ],
- ],
- qs => {
- dfs => "boolean",
- field_statistics => "boolean",
- fields => "list",
- filter_path => "list",
- offsets => "boolean",
- parent => "string",
- payloads => "boolean",
- positions => "boolean",
- preference => "string",
- realtime => "boolean",
- routing => "string",
- term_statistics => "boolean",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'update' => {
- body => {},
- doc => "docs-update",
- method => "POST",
- parts => {
- id => { required => 1 },
- index => { required => 1 },
- type => { required => 1 },
- },
- paths => [
- [ { id => 2, index => 0, type => 1 },
- "{index}", "{type}", "{id}", "_update",
- ],
- ],
- qs => {
- consistency => "enum",
- fields => "list",
- filter_path => "list",
- lang => "string",
- parent => "string",
- refresh => "boolean",
- retry_on_conflict => "number",
- routing => "string",
- script => "string",
- script_id => "string",
- scripted_upsert => "boolean",
- timeout => "time",
- timestamp => "time",
- ttl => "time",
- version => "number",
- version_type => "enum",
- },
- },
-
- 'update_by_query' => {
- body => {},
- doc => "plugins-reindex",
- method => "POST",
- parts => {
- index => { multi => 1, required => 1 },
- type => { multi => 1 }
- },
- paths => [
- [ { index => 0, type => 1 }, "{index}",
- "{type}", "_update_by_query",
- ],
- [ { index => 0 }, "{index}", "_update_by_query" ],
- ],
- qs => {
- _source => "list",
- _source_exclude => "list",
- _source_include => "list",
- allow_no_indices => "boolean",
- analyze_wildcard => "boolean",
- analyzer => "string",
- conflicts => "enum",
- consistency => "enum",
- default_operator => "enum",
- df => "string",
- expand_wildcards => "enum",
- explain => "boolean",
- fielddata_fields => "list",
- fields => "list",
- filter_path => "list",
- from => "number",
- ignore_unavailable => "boolean",
- lenient => "boolean",
- lowercase_expanded_terms => "boolean",
- preference => "string",
- q => "string",
- refresh => "boolean",
- request_cache => "boolean",
- requests_per_second => "number",
- routing => "list",
- scroll => "time",
- scroll_size => "number",
- search_timeout => "time",
- search_type => "enum",
- size => "number",
- sort => "list",
- stats => "list",
- suggest_field => "string",
- suggest_mode => "enum",
- suggest_size => "number",
- suggest_text => "string",
- terminate_after => "number",
- timeout => "time",
- track_scores => "boolean",
- version => "boolean",
- version_type => "boolean",
- wait_for_completion => "boolean",
- },
- },
-
- 'cat.aliases' => {
- doc => "cat-alias",
- parts => { name => { multi => 1 } },
- paths => [
- [ { name => 2 }, "_cat", "aliases", "{name}" ],
- [ {}, "_cat", "aliases" ],
- ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.allocation' => {
- doc => "cat-allocation",
- parts => { node_id => { multi => 1 } },
- paths => [
- [ { node_id => 2 }, "_cat", "allocation", "{node_id}" ],
- [ {}, "_cat", "allocation" ],
- ],
- qs => {
- bytes => "enum",
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.count' => {
- doc => "cat-count",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 2 }, "_cat", "count", "{index}" ],
- [ {}, "_cat", "count" ],
- ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.fielddata' => {
- doc => "cat-fielddata",
- parts => { fields => { multi => 1 } },
- paths => [
- [ { fields => 2 }, "_cat", "fielddata", "{fields}" ],
- [ {}, "_cat", "fielddata" ],
- ],
- qs => {
- bytes => "enum",
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.health' => {
- doc => "cat-health",
- parts => {},
- paths => [ [ {}, "_cat", "health" ] ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- ts => "boolean",
- v => "boolean",
- },
- },
-
- 'cat.help' => {
- doc => "cat",
- parts => {},
- paths => [ [ {}, "_cat" ] ],
- qs => { help => "boolean" },
- },
-
- 'cat.indices' => {
- doc => "cat-indices",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 2 }, "_cat", "indices", "{index}" ],
- [ {}, "_cat", "indices" ],
- ],
- qs => {
- bytes => "enum",
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- pri => "boolean",
- v => "boolean",
- },
- },
-
- 'cat.master' => {
- doc => "cat-master",
- parts => {},
- paths => [ [ {}, "_cat", "master" ] ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.nodeattrs' => {
- doc => "cat-nodeattrs",
- parts => {},
- paths => [ [ {}, "_cat", "nodeattrs" ] ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.nodes' => {
- doc => "cat-nodes",
- parts => {},
- paths => [ [ {}, "_cat", "nodes" ] ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.pending_tasks' => {
- doc => "cat-pending-tasks",
- parts => {},
- paths => [ [ {}, "_cat", "pending_tasks" ] ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.plugins' => {
- doc => "cat-plugins",
- parts => {},
- paths => [ [ {}, "_cat", "plugins" ] ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.recovery' => {
- doc => "cat-recovery",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 2 }, "_cat", "recovery", "{index}" ],
- [ {}, "_cat", "recovery" ],
- ],
- qs => {
- bytes => "enum",
- h => "list",
- help => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.repositories' => {
- doc => "cat-repositories",
- parts => {},
- paths => [ [ {}, "_cat", "repositories" ] ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.segments' => {
- doc => "cat-segments",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 2 }, "_cat", "segments", "{index}" ],
- [ {}, "_cat", "segments" ],
- ],
- qs => { h => "list", help => "boolean", v => "boolean" },
- },
-
- 'cat.shards' => {
- doc => "cat-shards",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 2 }, "_cat", "shards", "{index}" ],
- [ {}, "_cat", "shards" ],
- ],
- qs => {
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.snapshots' => {
- doc => "cat-snapshots",
- parts => { repository => { multi => 1, required => 1 } },
- paths =>
- [ [ { repository => 2 }, "_cat", "snapshots", "{repository}" ] ],
- qs => {
- h => "list",
- help => "boolean",
- ignore_unavailable => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cat.thread_pool' => {
- doc => "cat-thread-pool",
- parts => {},
- paths => [ [ {}, "_cat", "thread_pool" ] ],
- qs => {
- full_id => "boolean",
- h => "list",
- help => "boolean",
- local => "boolean",
- master_timeout => "time",
- v => "boolean",
- },
- },
-
- 'cluster.get_settings' => {
- doc => "cluster-update-settings",
- parts => {},
- paths => [ [ {}, "_cluster", "settings" ] ],
- qs => {
- filter_path => "list",
- flat_settings => "boolean",
- master_timeout => "time",
- timeout => "time",
- },
- },
-
- 'cluster.health' => {
- doc => "cluster-health",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 2 }, "_cluster", "health", "{index}" ],
- [ {}, "_cluster", "health" ],
- ],
- qs => {
- filter_path => "list",
- level => "enum",
- local => "boolean",
- master_timeout => "time",
- timeout => "time",
- wait_for_active_shards => "number",
- wait_for_nodes => "string",
- wait_for_relocating_shards => "number",
- wait_for_status => "enum",
- },
- },
-
- 'cluster.pending_tasks' => {
- doc => "cluster-pending",
- parts => {},
- paths => [ [ {}, "_cluster", "pending_tasks" ] ],
- qs => {
- filter_path => "list",
- local => "boolean",
- master_timeout => "time"
- },
- },
-
- 'cluster.put_settings' => {
- body => {},
- doc => "cluster-update-settings",
- method => "PUT",
- parts => {},
- paths => [ [ {}, "_cluster", "settings" ] ],
- qs => {
- filter_path => "list",
- flat_settings => "boolean",
- master_timeout => "time",
- timeout => "time",
- },
- },
-
- 'cluster.reroute' => {
- body => {},
- doc => "cluster-reroute",
- method => "POST",
- parts => {},
- paths => [ [ {}, "_cluster", "reroute" ] ],
- qs => {
- dry_run => "boolean",
- explain => "boolean",
- filter_path => "list",
- master_timeout => "time",
- metric => "list",
- timeout => "time",
- },
- },
-
- 'cluster.state' => {
- doc => "cluster-state",
- parts => { index => { multi => 1 }, metric => { multi => 1 } },
- paths => [
- [ { index => 3, metric => 2 }, "_cluster",
- "state", "{metric}",
- "{index}",
- ],
- [ { metric => 2 }, "_cluster", "state", "{metric}" ],
- [ {}, "_cluster", "state" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- flat_settings => "boolean",
- ignore_unavailable => "boolean",
- local => "boolean",
- master_timeout => "time",
- },
- },
-
- 'cluster.stats' => {
- doc => "cluster-stats",
- parts => { node_id => { multi => 1 } },
- paths => [
- [ { node_id => 3 }, "_cluster", "stats", "nodes", "{node_id}" ],
- [ {}, "_cluster", "stats" ],
- ],
- qs => {
- filter_path => "list",
- flat_settings => "boolean",
- human => "boolean",
- timeout => "time",
- },
- },
-
- 'indices.analyze' => {
- body => {},
- doc => "indices-analyze",
- parts => { index => {} },
- paths =>
- [ [ { index => 0 }, "{index}", "_analyze" ], [ {}, "_analyze" ] ],
- qs => {
- analyzer => "string",
- attributes => "list",
- char_filter => "list",
- char_filters => "list",
- explain => "boolean",
- field => "string",
- filter => "list",
- filter_path => "list",
- filters => "list",
- format => "enum",
- prefer_local => "boolean",
- text => "list",
- tokenizer => "string",
- },
- },
-
- 'indices.clear_cache' => {
- doc => "indices-clearcache",
- method => "POST",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_cache", "clear" ],
- [ {}, "_cache", "clear" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- fielddata => "boolean",
- fields => "list",
- filter_path => "list",
- ignore_unavailable => "boolean",
- query => "boolean",
- recycler => "boolean",
- request => "boolean",
- },
- },
-
- 'indices.close' => {
- doc => "indices-open-close",
- method => "POST",
- parts => { index => { multi => 1, required => 1 } },
- paths => [ [ { index => 0 }, "{index}", "_close" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- master_timeout => "time",
- timeout => "time",
- },
- },
-
- 'indices.create' => {
- body => {},
- doc => "indices-create-index",
- method => "PUT",
- parts => { index => { required => 1 } },
- paths => [ [ { index => 0 }, "{index}" ] ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time",
- update_all_types => "boolean",
- },
- },
-
- 'indices.delete' => {
- doc => "indices-delete-index",
- method => "DELETE",
- parts => { index => { multi => 1, required => 1 } },
- paths => [ [ { index => 0 }, "{index}" ] ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time"
- },
- },
-
- 'indices.delete_alias' => {
- doc => "indices-aliases",
- method => "DELETE",
- parts => {
- index => { multi => 1, required => 1 },
- name => { multi => 1, required => 1 },
- },
- paths =>
- [ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ] ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time"
- },
- },
-
- 'indices.delete_template' => {
- doc => "indices-templates",
- method => "DELETE",
- parts => { name => { required => 1 } },
- paths => [ [ { name => 1 }, "_template", "{name}" ] ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time"
- },
- },
-
- 'indices.delete_warmer' => {
- doc => "indices-warmers",
- method => "DELETE",
- parts => {
- index => { multi => 1, required => 1 },
- name => { multi => 1, required => 1 },
- },
- paths =>
- [ [ { index => 0, name => 2 }, "{index}", "_warmer", "{name}" ] ],
- qs => { filter_path => "list", master_timeout => "time" },
- },
-
- 'indices.exists' => {
- doc => "indices-exists",
- method => "HEAD",
- parts => { index => { multi => 1, required => 1 } },
- paths => [ [ { index => 0 }, "{index}" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- ignore_unavailable => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.exists_alias' => {
- doc => "indices-aliases",
- method => "HEAD",
- parts => { index => { multi => 1 }, name => { multi => 1 } },
- paths => [
- [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ],
- [ { index => 0 }, "{index}", "_alias" ],
- [ { name => 1 }, "_alias", "{name}" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- ignore_unavailable => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.exists_template' => {
- doc => "indices-templates",
- method => "HEAD",
- parts => { name => { required => 1 } },
- paths => [ [ { name => 1 }, "_template", "{name}" ] ],
- qs => { local => "boolean", master_timeout => "time" },
- },
-
- 'indices.exists_type' => {
- doc => "indices-types-exists",
- method => "HEAD",
- parts => {
- index => { multi => 1, required => 1 },
- type => { multi => 1, required => 1 },
- },
- paths => [ [ { index => 0, type => 1 }, "{index}", "{type}" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- ignore_unavailable => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.flush' => {
- doc => "indices-flush",
- method => "POST",
- parts => { index => { multi => 1 } },
- paths =>
- [ [ { index => 0 }, "{index}", "_flush" ], [ {}, "_flush" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- force => "boolean",
- ignore_unavailable => "boolean",
- wait_if_ongoing => "boolean",
- },
- },
-
- 'indices.flush_synced' => {
- doc => "indices-synced-flush",
- method => "POST",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_flush", "synced" ],
- [ {}, "_flush", "synced" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- },
- },
-
- 'indices.forcemerge' => {
- doc => "indices-forcemerge",
- method => "POST",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_forcemerge" ],
- [ {}, "_forcemerge" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- flush => "boolean",
- ignore_unavailable => "boolean",
- max_num_segments => "number",
- only_expunge_deletes => "boolean",
- wait_for_merge => "boolean",
- },
- },
-
- 'indices.get' => {
- doc => "indices-get-index",
- parts => {
- feature => { multi => 1 },
- index => { multi => 1, required => 1 }
- },
- paths => [
- [ { feature => 1, index => 0 }, "{index}", "{feature}" ],
- [ { index => 0 }, "{index}" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- flat_settings => "boolean",
- human => "boolean",
- ignore_unavailable => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.get_alias' => {
- doc => "indices-aliases",
- parts => { index => { multi => 1 }, name => { multi => 1 } },
- paths => [
- [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ],
- [ { index => 0 }, "{index}", "_alias" ],
- [ { name => 1 }, "_alias", "{name}" ],
- [ {}, "_alias" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.get_aliases' => {
- doc => "indices-aliases",
- parts => { index => { multi => 1 }, name => { multi => 1 } },
- paths => [
- [ { index => 0, name => 2 }, "{index}", "_aliases", "{name}" ],
- [ { index => 0 }, "{index}", "_aliases" ],
- [ { name => 1 }, "_aliases", "{name}" ],
- [ {}, "_aliases" ],
- ],
- qs =>
- { filter_path => "list", local => "boolean", timeout => "time" },
- },
-
- 'indices.get_field_mapping' => {
- doc => "indices-get-field-mapping",
- parts => {
- fields => { multi => 1, required => 1 },
- index => { multi => 1 },
- type => { multi => 1 },
- },
- paths => [
- [ { fields => 4, index => 0, type => 2 }, "{index}",
- "_mapping", "{type}",
- "field", "{fields}",
- ],
- [ { fields => 3, index => 0 }, "{index}",
- "_mapping", "field",
- "{fields}",
- ],
- [ { fields => 3, type => 1 }, "_mapping",
- "{type}", "field",
- "{fields}",
- ],
- [ { fields => 2 }, "_mapping", "field", "{fields}" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- include_defaults => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.get_mapping' => {
- doc => "indices-get-mapping",
- parts => { index => { multi => 1 }, type => { multi => 1 } },
- paths => [
- [ { index => 0, type => 2 }, "{index}", "_mapping", "{type}" ],
- [ { index => 0 }, "{index}", "_mapping" ],
- [ { type => 1 }, "_mapping", "{type}" ],
- [ {}, "_mapping" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.get_settings' => {
- doc => "indices-get-settings",
- parts => { index => { multi => 1 }, name => { multi => 1 } },
- paths => [
- [ { index => 0, name => 2 }, "{index}", "_settings", "{name}" ],
- [ { index => 0 }, "{index}", "_settings" ],
- [ { name => 1 }, "_settings", "{name}" ],
- [ {}, "_settings" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- flat_settings => "boolean",
- human => "boolean",
- ignore_unavailable => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.get_template' => {
- doc => "indices-templates",
- parts => { name => { multi => 1 } },
- paths =>
- [ [ { name => 1 }, "_template", "{name}" ], [ {}, "_template" ] ],
- qs => {
- filter_path => "list",
- flat_settings => "boolean",
- local => "boolean",
- master_timeout => "time",
- },
- },
-
- 'indices.get_upgrade' => {
- doc => "indices-upgrade",
- parts => { index => { multi => 1 } },
- paths =>
- [ [ { index => 0 }, "{index}", "_upgrade" ], [ {}, "_upgrade" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- human => "boolean",
- ignore_unavailable => "boolean",
- },
- },
-
- 'indices.get_warmer' => {
- doc => "indices-warmers",
- parts => {
- index => { multi => 1 },
- name => { multi => 1 },
- type => { multi => 1 }
- },
- paths => [
- [ { index => 0, name => 3, type => 1 },
- "{index}", "{type}", "_warmer", "{name}",
- ],
- [ { index => 0, name => 2 }, "{index}", "_warmer", "{name}" ],
- [ { index => 0 }, "{index}", "_warmer" ],
- [ { name => 1 }, "_warmer", "{name}" ],
- [ {}, "_warmer" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- local => "boolean",
- },
- },
-
- 'indices.open' => {
- doc => "indices-open-close",
- method => "POST",
- parts => { index => { multi => 1, required => 1 } },
- paths => [ [ { index => 0 }, "{index}", "_open" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- master_timeout => "time",
- timeout => "time",
- },
- },
-
- 'indices.optimize' => {
- doc => "indices-optimize",
- method => "POST",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_optimize" ],
- [ {}, "_optimize" ]
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- flush => "boolean",
- ignore_unavailable => "boolean",
- max_num_segments => "number",
- only_expunge_deletes => "boolean",
- wait_for_merge => "boolean",
- },
- },
-
- 'indices.put_alias' => {
- body => {},
- doc => "indices-aliases",
- method => "PUT",
- parts => {
- index => { multi => 1, required => 1 },
- name => { required => 1 }
- },
- paths =>
- [ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ] ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time"
- },
- },
-
- 'indices.put_mapping' => {
- body => { required => 1 },
- doc => "indices-put-mapping",
- method => "PUT",
- parts => { index => { multi => 1 }, type => { required => 1 } },
- paths => [
- [ { index => 0, type => 2 }, "{index}", "_mapping", "{type}" ],
- [ { type => 1 }, "_mapping", "{type}" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- master_timeout => "time",
- timeout => "time",
- update_all_types => "boolean",
- },
- },
-
- 'indices.put_settings' => {
- body => { required => 1 },
- doc => "indices-update-settings",
- method => "PUT",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_settings" ],
- [ {}, "_settings" ]
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- flat_settings => "boolean",
- ignore_unavailable => "boolean",
- master_timeout => "time",
- },
- },
-
- 'indices.put_template' => {
- body => { required => 1 },
- doc => "indices-templates",
- method => "PUT",
- parts => { name => { required => 1 } },
- paths => [ [ { name => 1 }, "_template", "{name}" ] ],
- qs => {
- create => "boolean",
- filter_path => "list",
- flat_settings => "boolean",
- master_timeout => "time",
- order => "number",
- timeout => "time",
- },
- },
-
- 'indices.put_warmer' => {
- body => { required => 1 },
- doc => "indices-warmers",
- method => "PUT",
- parts => {
- index => { multi => 1 },
- name => { required => 1 },
- type => { multi => 1 },
- },
- paths => [
- [ { index => 0, name => 3, type => 1 },
- "{index}", "{type}", "_warmer", "{name}",
- ],
- [ { index => 0, name => 2 }, "{index}", "_warmer", "{name}" ],
- [ { name => 1 }, "_warmer", "{name}" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- master_timeout => "time",
- request_cache => "boolean",
- },
- },
-
- 'indices.recovery' => {
- doc => "indices-recovery",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_recovery" ],
- [ {}, "_recovery" ]
- ],
- qs => {
- active_only => "boolean",
- detailed => "boolean",
- filter_path => "list",
- human => "boolean",
- },
- },
-
- 'indices.refresh' => {
- doc => "indices-refresh",
- method => "POST",
- parts => { index => { multi => 1 } },
- paths =>
- [ [ { index => 0 }, "{index}", "_refresh" ], [ {}, "_refresh" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- force => "boolean",
- ignore_unavailable => "boolean",
- },
- },
-
- 'indices.segments' => {
- doc => "indices-segments",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_segments" ],
- [ {}, "_segments" ]
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- human => "boolean",
- ignore_unavailable => "boolean",
- verbose => "boolean",
- },
- },
-
- 'indices.shard_stores' => {
- doc => "indices-shards-stores",
- parts => { index => { multi => 1 } },
- paths => [
- [ { index => 0 }, "{index}", "_shard_stores" ],
- [ {}, "_shard_stores" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- status => "list",
- },
- },
-
- 'indices.stats' => {
- doc => "indices-stats",
- parts => { index => { multi => 1 }, metric => { multi => 1 } },
- paths => [
- [ { index => 0, metric => 2 }, "{index}", "_stats", "{metric}" ],
- [ { index => 0 }, "{index}", "_stats" ],
- [ { metric => 1 }, "_stats", "{metric}" ],
- [ {}, "_stats" ],
- ],
- qs => {
- completion_fields => "list",
- fielddata_fields => "list",
- fields => "list",
- filter_path => "list",
- groups => "list",
- human => "boolean",
- level => "enum",
- types => "list",
- },
- },
-
- 'indices.update_aliases' => {
- body => { required => 1 },
- doc => "indices-aliases",
- method => "POST",
- parts => {},
- paths => [ [ {}, "_aliases" ] ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time"
- },
- },
-
- 'indices.upgrade' => {
- doc => "indices-upgrade",
- method => "POST",
- parts => { index => { multi => 1 } },
- paths =>
- [ [ { index => 0 }, "{index}", "_upgrade" ], [ {}, "_upgrade" ] ],
- qs => {
- allow_no_indices => "boolean",
- expand_wildcards => "enum",
- filter_path => "list",
- ignore_unavailable => "boolean",
- only_ancient_segments => "boolean",
- wait_for_completion => "boolean",
- },
- },
-
- 'indices.validate_query' => {
- body => {},
- doc => "search-validate",
- parts => { index => { multi => 1 }, type => { multi => 1 } },
- paths => [
- [ { index => 0, type => 1 }, "{index}",
- "{type}", "_validate",
- "query",
- ],
- [ { index => 0 }, "{index}", "_validate", "query" ],
- [ {}, "_validate", "query" ],
- ],
- qs => {
- allow_no_indices => "boolean",
- analyze_wildcard => "boolean",
- analyzer => "string",
- default_operator => "enum",
- df => "string",
- expand_wildcards => "enum",
- explain => "boolean",
- filter_path => "list",
- ignore_unavailable => "boolean",
- lenient => "boolean",
- lowercase_expanded_terms => "boolean",
- q => "string",
- rewrite => "boolean",
- },
- },
-
- 'nodes.hot_threads' => {
- doc => "cluster-nodes-hot-threads",
- parts => { node_id => { multi => 1 } },
- paths => [
- [ { node_id => 1 }, "_nodes", "{node_id}", "hot_threads" ],
- [ {}, "_nodes", "hot_threads" ],
- ],
- qs => {
- filter_path => "list",
- ignore_idle_threads => "boolean",
- interval => "time",
- snapshots => "number",
- threads => "number",
- timeout => "time",
- type => "enum",
- },
- },
-
- 'nodes.info' => {
- doc => "cluster-nodes-info",
- parts => { metric => { multi => 1 }, node_id => { multi => 1 } },
- paths => [
- [ { metric => 2, node_id => 1 }, "_nodes",
- "{node_id}", "{metric}",
- ],
- [ { metric => 1 }, "_nodes", "{metric}" ],
- [ { node_id => 1 }, "_nodes", "{node_id}" ],
- [ {}, "_nodes" ],
- ],
- qs => {
- filter_path => "list",
- flat_settings => "boolean",
- human => "boolean",
- timeout => "time",
- },
- },
-
- 'nodes.stats' => {
- doc => "cluster-nodes-stats",
- parts => {
- index_metric => { multi => 1 },
- metric => { multi => 1 },
- node_id => { multi => 1 },
- },
- paths => [
- [ { index_metric => 4, metric => 3, node_id => 1 },
- "_nodes", "{node_id}", "stats", "{metric}", "{index_metric}",
- ],
- [ { index_metric => 3, metric => 2 }, "_nodes",
- "stats", "{metric}",
- "{index_metric}",
- ],
- [ { metric => 3, node_id => 1 }, "_nodes",
- "{node_id}", "stats",
- "{metric}",
- ],
- [ { metric => 2 }, "_nodes", "stats", "{metric}" ],
- [ { node_id => 1 }, "_nodes", "{node_id}", "stats" ],
- [ {}, "_nodes", "stats" ],
- ],
- qs => {
- completion_fields => "list",
- fielddata_fields => "list",
- fields => "list",
- filter_path => "list",
- groups => "boolean",
- human => "boolean",
- level => "enum",
- timeout => "time",
- types => "list",
- },
- },
-
- 'snapshot.create' => {
- body => {},
- doc => "modules-snapshots",
- method => "PUT",
- parts => {
- repository => { required => 1 },
- snapshot => { required => 1 }
- },
- paths => [
- [ { repository => 1, snapshot => 2 }, "_snapshot",
- "{repository}", "{snapshot}",
- ],
- ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- wait_for_completion => "boolean",
- },
- },
-
- 'snapshot.create_repository' => {
- body => { required => 1 },
- doc => "modules-snapshots",
- method => "PUT",
- parts => { repository => { required => 1 } },
- paths => [ [ { repository => 1 }, "_snapshot", "{repository}" ] ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time",
- verify => "boolean",
- },
- },
-
- 'snapshot.delete' => {
- doc => "modules-snapshots",
- method => "DELETE",
- parts => {
- repository => { required => 1 },
- snapshot => { required => 1 }
- },
- paths => [
- [ { repository => 1, snapshot => 2 }, "_snapshot",
- "{repository}", "{snapshot}",
- ],
- ],
- qs => { filter_path => "list", master_timeout => "time" },
- },
-
- 'snapshot.delete_repository' => {
- doc => "modules-snapshots",
- method => "DELETE",
- parts => { repository => { multi => 1, required => 1 } },
- paths => [ [ { repository => 1 }, "_snapshot", "{repository}" ] ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time"
- },
- },
-
- 'snapshot.get' => {
- doc => "modules-snapshots",
- parts => {
- repository => { required => 1 },
- snapshot => { multi => 1, required => 1 },
- },
- paths => [
- [ { repository => 1, snapshot => 2 }, "_snapshot",
- "{repository}", "{snapshot}",
- ],
- ],
- qs => { filter_path => "list", master_timeout => "time" },
- },
-
- 'snapshot.get_repository' => {
- doc => "modules-snapshots",
- parts => { repository => { multi => 1 } },
- paths => [
- [ { repository => 1 }, "_snapshot", "{repository}" ],
- [ {}, "_snapshot" ],
- ],
- qs => {
- filter_path => "list",
- local => "boolean",
- master_timeout => "time"
- },
- },
-
- 'snapshot.restore' => {
- body => {},
- doc => "modules-snapshots",
- method => "POST",
- parts => {
- repository => { required => 1 },
- snapshot => { required => 1 }
- },
- paths => [
- [ { repository => 1, snapshot => 2 }, "_snapshot",
- "{repository}", "{snapshot}",
- "_restore",
- ],
- ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- wait_for_completion => "boolean",
- },
- },
-
- 'snapshot.status' => {
- doc => "modules-snapshots",
- parts => { repository => {}, snapshot => { multi => 1 } },
- paths => [
- [ { repository => 1, snapshot => 2 }, "_snapshot",
- "{repository}", "{snapshot}",
- "_status",
- ],
- [ { repository => 1 }, "_snapshot", "{repository}", "_status" ],
- [ {}, "_snapshot", "_status" ],
- ],
- qs => { filter_path => "list", master_timeout => "time" },
- },
-
- 'snapshot.verify_repository' => {
- doc => "modules-snapshots",
- method => "POST",
- parts => { repository => { required => 1 } },
- paths => [
- [ { repository => 1 }, "_snapshot", "{repository}", "_verify" ],
- ],
- qs => {
- filter_path => "list",
- master_timeout => "time",
- timeout => "time"
- },
- },
-
- 'tasks.cancel' => {
- doc => "tasks-cancel",
- method => "POST",
- parts => { task_id => {} },
- paths => [
- [ { task_id => 1 }, "_tasks", "{task_id}", "_cancel" ],
- [ {}, "_tasks", "_cancel" ],
- ],
- qs => {
- actions => "list",
- filter_path => "list",
- node_id => "list",
- parent_node => "string",
- parent_task => "string",
- },
- },
-
- 'tasks.list' => {
- doc => "tasks-list",
- parts => { task_id => {} },
- paths =>
- [ [ { task_id => 1 }, "_tasks", "{task_id}" ], [ {}, "_tasks" ] ],
- qs => {
- actions => "list",
- detailed => "boolean",
- filter_path => "list",
- node_id => "list",
- parent_node => "string",
- parent_task => "string",
- wait_for_completion => "boolean",
- },
- },
-
-#=== AUTOGEN - END ===
-
-);
-
-__PACKAGE__->_qs_init( \%API );
-1;
-
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Role::API - This class contains the spec for the Elasticsearch APIs
-
-=head1 VERSION
-
-version 6.81
-
-=head1 DESCRIPTION
-
-All of the Elasticsearch APIs are defined in this role. The example given below
-is the definition for the L<Search::Elasticsearch::Client::2_0::Direct/index()> method:
-
- 'index' => {
- body => { required => 1 },
- doc => "docs-index_",
- method => "POST",
- parts => {
- id => {},
- index => { required => 1 },
- type => { required => 1 }
- },
- paths => [
- [ { id => 2, index => 0, type => 1 }, "{index}",
- "{type}", "{id}"
- ],
- [ { index => 0, type => 1 }, "{index}", "{type}" ],
- ],
- qs => {
- consistency => "enum",
- filter_path => "list",
- op_type => "enum",
- parent => "string",
- refresh => "boolean",
- routing => "string",
- timeout => "time",
- timestamp => "time",
- ttl => "time",
- version => "number",
- version_type => "enum",
- },
- },
-
-These definitions can be used by different L<Search::Elasticsearch::Role::Client>
-implementations to provide distinct user interfaces.
-
-=head1 METHODS
-
-=head2 C<api()>
-
- $defn = $api->api($name);
-
-The only method in this class is the C<api()> method which takes the name
-of the I<action> and returns its definition. Actions in the
-C<indices> or C<cluster> namespace use the namespace as a prefix, eg:
-
- $defn = $e->api('indices.create');
- $defn = $e->api('cluster.node_stats');
-
-=head1 SEE ALSO
-
-=over
-
-=item *
-
-L<Search::Elasticsearch::Role::API>
-
-=item *
-
-L<Search::Elasticsearch::Client::2_0::Direct>
-
-=back
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
-
-# ABSTRACT: This class contains the spec for the Elasticsearch APIs
-
diff --git a/lib/Search/Elasticsearch/Client/2_0/TestServer.pm b/lib/Search/Elasticsearch/Client/2_0/TestServer.pm
deleted file mode 100644
index 468fa33..0000000
--- a/lib/Search/Elasticsearch/Client/2_0/TestServer.pm
+++ /dev/null
@@ -1,59 +0,0 @@
-package Search::Elasticsearch::Client::2_0::TestServer;
-$Search::Elasticsearch::Client::2_0::TestServer::VERSION = '6.81';
-use strict;
-use warnings;
-
-#===================================
-sub command_line {
-#===================================
- my ( $class, $ts, $pid_file, $dir, $transport, $http ) = @_;
-
- return (
- $ts->es_home . '/bin/elasticsearch',
- '-p',
- $pid_file->filename,
- map {"-Des.$_"} (
- 'path.data=' . $dir,
- 'network.host=127.0.0.1',
- 'cluster.name=es_test',
- 'discovery.zen.ping_timeout=1s',
- 'discovery.zen.ping.multicast.enabled=false',
- 'discovery.zen.ping.unicast.hosts=127.0.0.1:' . $ts->es_port,
- 'transport.tcp.port=' . $transport,
- 'http.port=' . $http,
- @{ $ts->conf }
- )
- );
-}
-
-1
-
-# ABSTRACT: Client-specific backend for Search::Elasticsearch::TestServer
-
-__END__
-
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::TestServer - Client-specific backend for Search::Elasticsearch::TestServer
-
-=head1 VERSION
-
-version 6.81
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
diff --git a/lib/Search/Elasticsearch/Client/7_0.pm b/lib/Search/Elasticsearch/Client/7_0.pm
new file mode 100644
index 0000000..6449809
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0.pm
@@ -0,0 +1,72 @@
+# 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 Search::Elasticsearch::Client::7_0;
+
+our $VERSION='8.00';
+use Search::Elasticsearch 8.00 ();
+
+1;
+
+__END__
+
+# ABSTRACT: Thin client with full support for Elasticsearch 7.x APIs
+
+=head1 DESCRIPTION
+
+The L<Search::Elasticsearch::Client::7_0> package provides a client
+compatible with Elasticsearch 7.x. It should be used in conjunction
+with L<Search::Elasticsearch> as follows:
+
+ $e = Search::Elasticsearch->new(
+ client => "7_0::Direct"
+ );
+
+See L<Search::Elasticsearch::Client::7_0::Direct> for documentation
+about how to use the client itself.
+
+=head1 PREVIOUS VERSIONS OF ELASTICSEARCH
+
+This version of the client supports the Elasticsearch 7.0 branch,
+which is not backwards compatible with earlier branches.
+
+If you need to talk to a version of Elasticsearch before 7.0.0, please
+install one of the following packages:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Client::6_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::5_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::2_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::1_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::0_90>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/7_0/Async.pm b/lib/Search/Elasticsearch/Client/7_0/Async.pm
new file mode 100644
index 0000000..b46702f
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Async.pm
@@ -0,0 +1,72 @@
+# 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 Search::Elasticsearch::Client::7_0::Async;
+
+our $VERSION='8.00';
+use Search::Elasticsearch::Client::7_0 7.00 ();
+
+1;
+
+__END__
+
+# ABSTRACT: Thin async client with full support for Elasticsearch 7.x APIs
+
+=head1 DESCRIPTION
+
+The L<Search::Elasticsearch::Client::7_0::Async> package provides a client
+compatible with Elasticsearch 7.x. It should be used in conjunction
+with L<Search::Elasticsearch::Async> as follows:
+
+ $e = Search::Elasticsearch::Async->new(
+ client => "7_0::Direct"
+ );
+
+See L<Search::Elasticsearch::Client::7_0::Direct> for documentation
+about how to use the client itself.
+
+=head1 PREVIOUS VERSIONS OF ELASTICSEARCH
+
+This version of the client supports the Elasticsearch 7.0 branch,
+which is not backwards compatible with earlier branches.
+
+If you need to talk to a version of Elasticsearch before 7.0.0, please
+install one of the following packages:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Client::6_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::5_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::2_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::1_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::0_90::Async>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/7_0/Async/Bulk.pm b/lib/Search/Elasticsearch/Client/7_0/Async/Bulk.pm
new file mode 100644
index 0000000..1e664bb
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Async/Bulk.pm
@@ -0,0 +1,498 @@
+# 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 Search::Elasticsearch::Client::7_0::Async::Bulk;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::Bulk',
+ 'Search::Elasticsearch::Role::Is_Async';
+
+use Search::Elasticsearch::Util qw(parse_params throw);
+use Scalar::Util qw(weaken blessed);
+use Promises qw(deferred);
+use Try::Tiny;
+use namespace::clean;
+
+has 'on_fatal' => ( is => 'lazy' );
+
+#===================================
+sub _build_on_fatal {
+#===================================
+ my $self = shift;
+ return sub {
+ warn("Fatal bulk error: @_");
+ };
+}
+
+#===================================
+sub add_action {
+#===================================
+ my $self = shift;
+ my $buffer = $self->_buffer;
+ my $max_size = $self->max_size;
+ my $max_count = $self->max_count;
+ my $max_time = $self->max_time;
+
+ my $deferred = deferred;
+ my @actions = @_;
+
+ my $weak_add;
+ my $add = sub {
+ while (@actions) {
+ my @json = try {
+ $self->_encode_action( splice( @actions, 0, 2 ) );
+ }
+ catch {
+ $self->on_fatal->($_);
+ $deferred->reject($_);
+ ();
+ };
+ return unless @json;
+
+ push @$buffer, @json;
+
+ my $size = $self->_buffer_size;
+ $size += length($_) + 1 for @json;
+ $self->_buffer_size($size);
+
+ my $count = $self->_buffer_count( $self->_buffer_count + 1 );
+
+ next
+ unless ( $max_size and $size >= $max_size )
+ || ( $max_count and $count >= $max_count )
+ || ( $max_time and time >= $self->_last_flush + $max_time );
+
+ return $self->flush->done( $weak_add,
+ sub { $deferred->reject(@_) } );
+ }
+ return $deferred->resolve;
+
+ };
+
+ weaken( $weak_add = $add );
+ $add->();
+ return $deferred->promise;
+
+}
+
+#===================================
+sub flush {
+#===================================
+ my $self = shift;
+
+ my $size = $self->_buffer_size;
+ my $count = $self->_buffer_count;
+
+ $self->_last_flush(time);
+
+ unless ($size) {
+ return deferred->resolve( { items => [] } )->promise;
+ }
+
+ my @items = ( @{ $self->_buffer } );
+ $self->clear_buffer;
+
+ if ( $self->verbose ) {
+ local $| = 1;
+ print ".";
+ }
+
+ my $promise
+ = $self->es->bulk( %{ $self->_bulk_args }, body => \@items )->catch(
+ sub {
+ my $error = shift;
+ if ( $error->is( 'Cxn', 'NoNodes' ) ) {
+ push @{ $self->_buffer }, @items;
+ $self->_buffer_size( $self->_buffer_size + $size );
+ $self->_buffer_count( $self->_buffer_count + $count );
+ }
+ die $error;
+ }
+ );
+ $promise->then( sub { $self->_report( \@items, @_ ) },
+ sub { $self->on_fatal(@_) } );
+ return $promise;
+}
+
+1;
+
+# ABSTRACT: A helper module for the Bulk API
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new;
+ my $bulk = $es->bulk_helper(
+ index => 'my_index',
+ type => 'my_type'
+ );
+
+ # Index docs:
+ $promise = $bulk->index({ id => 1, source => { foo => 'bar' }});
+ $promise = $bulk->add_action( index => { id => 1, source => { foo=> 'bar' }});
+
+ # Create docs:
+ $promise = $bulk->create({ id => 1, source => { foo => 'bar' }});
+ $promise = $bulk->add_action( create => { id => 1, source => { foo=> 'bar' }});
+ $promise = $bulk->create_docs({ foo => 'bar' })
+
+ # Delete docs:
+ $promise = $bulk->delete({ id => 1});
+ $promise = $bulk->add_action( delete => { id => 1 });
+ $promise = $bulk->delete_ids(1,2,3)
+
+ # Update docs:
+ $promise = $bulk->update({ id => 1, script => '...' });
+ $promise = $bulk->add_action( update => { id => 1, script => '...' });
+
+ # Manual flush
+ $promise = $bulk->flush;
+
+=head1 DESCRIPTION
+
+This module provides an async wrapper for the L<Search::Elasticsearch::Client::7_0::Direct/bulk()>
+method which makes it easier to run multiple create, index, update or delete
+actions in a single request.
+
+The L<Search::Elasticsearch::Client::7_0::Async::Bulk> module acts as a queue, buffering up actions
+until it reaches a maximum count of actions, or a maximum size of JSON request
+body, at which point it issues a C<bulk()> request.
+
+Once you have finished adding actions, call L</flush()> to force the final
+C<bulk()> request on the items left in the queue.
+
+This class does L<Search::Elasticsearch::Client::7_0::Role::Bulk> and
+L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 CREATING A NEW INSTANCE
+
+=head2 C<new()>
+
+ $bulk = $es->bulk_helper(
+
+ index => 'default_index', # optional
+ type => 'default_type', # optional
+ %other_bulk_params # optional
+
+ max_count => 1_000, # optional
+ max_size => 1_000_000, # optional
+ max_time => 6, # optional
+
+ verbose => 0 | 1, # optional
+
+ on_success => sub {...}, # optional
+ on_error => sub {...}, # optional
+ on_conflict => sub {...}, # optional
+ on_fatal => sub {...}, # optional
+
+ );
+
+The C<bulk_helper> method loads L<Search::Elasticsearch::Client::7_0::Async::Bulk>,
+calls L</new()> with the specified parameters and returns a new C<$bulk> object.
+
+The C<index> and C<type> parameters provide default values for
+C<index> and C<type>, which can be overridden in each action.
+You can also pass any other values which are accepted
+by the L<bulk()|Search::Elasticsearch::Client::7_0::Direct/bulk()> method.
+
+See L</flush()> for more information about the other parameters.
+
+=head1 FLUSHING THE BUFFER
+
+=head2 C<flush()>
+
+ $promise = $bulk->flush;
+
+The C<flush()> method sends all buffered actions to Elasticsearch using
+a L<bulk()|Search::Elasticsearch::Client::7_0::Direct/bulk()> request and returns
+a L<Promise>, which is rejected if the bulk request fails or if any of
+the C<on_success>, C<on_error> or C<on_conflict> callbacks throws an
+exception, otherwise it is resolved with the items that have been flushed.
+
+=head2 Auto-flushing
+
+An automatic L</flush()> is triggered whenever the C<max_count>, C<max_size>,
+or C<max_time> threshold is breached. This causes all actions in the buffer to be
+sent to Elasticsearch.
+
+=over
+
+=item * C<max_count>
+
+The maximum number of actions to allow before triggering a L</flush()>.
+This can be disabled by setting C<max_count> to C<0>. Defaults to
+C<1,000>.
+
+=item * C<max_size>
+
+The maximum size of JSON request body to allow before triggering a
+L</flush()>. This can be disabled by setting C<max_size> to C<0>. Defaults
+to C<1_000,000> bytes.
+
+=item * C<max_time>
+
+The maximum number of seconds to wait before triggering a flush. Defaults
+to C<0> seconds, which means that it is disabled. B<Note:> This timeout
+is only triggered when new items are added to the queue, not in the background.
+
+=back
+
+=head2 Errors when flushing
+
+There are two levels of error which can be thrown when L</flush()>
+is called, either manually or automatically.
+
+=over
+
+=item * Temporary Elasticsearch errors
+
+A C<Cxn> error like a C<NoNodes> error which indicates that your cluster is down.
+These errors do not clear the buffer, as they can be retried later on.
+These errors are reported via the C<on_fatal> callback and by rejecting
+the promise returned by L</flush()>, L</index()> etc.
+
+=item * Action errors
+
+Individual actions may fail. For instance, a C<create> action will fail
+if a document with the same C<index>, C<type> and C<id> already exists.
+These action errors are reported via L<callbacks|/Using callbacks>.
+
+=back
+
+=head2 Using callbacks
+
+By default, any I<Action errors> (see above) cause warnings to be
+written to C<STDERR>. However, you can use the C<on_error>, C<on_conflict>
+and C<on_success> callbacks for more fine-grained control.
+
+All callbacks receive the following arguments:
+
+=over
+
+=item C<$action>
+
+The name of the action, ie C<index>, C<create>, C<update> or C<delete>.
+
+=item C<$response>
+
+The response that Elasticsearch returned for this action.
+
+=item C<$i>
+
+The index of the action, ie the first action in the flush request
+will have C<$i> set to C<0>, the second will have C<$i> set to C<1> etc.
+
+=back
+
+=head3 C<on_success>
+
+ $bulk = $e->bulk_helper->new(
+ on_success => sub {
+ my ($action,$response,$i) = @_;
+ # do something
+ },
+ );
+
+The C<on_success> callback is called for every action that has a successful
+response.
+
+=head3 C<on_conflict>
+
+ $bulk = $e->bulk_helper->new(
+ on_conflict => sub {
+ my ($action,$response,$i,$version) = @_;
+ # do something
+ },
+ );
+
+The C<on_conflict> callback is called for actions that have triggered
+a C<Conflict> error, eg trying to C<create> a document which already
+exists. The C<$version> argument will contain the version number
+of the document currently stored in Elasticsearch (if found).
+
+=head3 C<on_error>
+
+ $bulk = $e->bulk_helper->new(
+ on_error => sub {
+ my ($action,$response,$i) = @_;
+ # do something
+ },
+ );
+
+The C<on_error> callback is called for any error (unless the C<on_conflict>)
+callback has already been called).
+
+=head2 Disabling callbacks and autoflush
+
+If you want to be in control of flushing, and you just want to receive
+the raw response that Elasticsearch sends instead of using callbacks,
+then you can do so as follows:
+
+ $bulk = $e->bulk_helper->new(
+ max_count => 0,
+ max_size => 0,
+ on_error => undef
+ );
+
+ $bulk->add_actions(....);
+ $bulk->flush
+ ->then(
+ sub { my $response = shift; ...},
+ sub { my $error = shift; ....}
+ )
+
+=head1 CREATE, INDEX, UPDATE, DELETE
+
+The L</add_action()>, L</create()>, L</create_docs()>, L</index()>,
+L</delete()>, L</delete_ids()> and L</update()> methods all return a Promise,
+which is resolved once the actions have been added to the queue and
+AFTER the queue has been flushed (if necessary). It is important
+to wait for the promise to be resolved before continuing to queue more
+items, otherwise the pending requests may fill up your available memory.
+
+For instance:
+
+ use Promises qw(deferred);
+ use Scalar::Util qw(weaken);
+
+ $bulk = $es->bulk_helper;
+
+ sub bulk_index {
+ my $d = deferred;
+ my $weak_cb;
+ my $cb = sub {
+ my @docs = get_next_docs_from_somewhere();
+ unless (@docs) {
+ return $d->resolve;
+ }
+ $bulk->index(@docs)
+ ->then(
+ $weak_cb,
+ sub { $d->reject(@_) }
+ );
+ };
+ weaken ($weak_cb = $cb);
+ $cb->();
+ $d->promise->then( sub {$b->flush} );
+ }
+
+=head2 C<add_action()>
+
+ $promise = $bulk->add_action(
+ create => { ...params... },
+ index => { ...params... },
+ update => { ...params... },
+ delete => { ...params... }
+ );
+
+The C<add_action()> method allows you to add multiple C<create>, C<index>,
+C<update> and C<delete> actions to the queue. The first value is the action
+type, and the second value is the parameters that describe that action.
+See the individual helper methods below for details.
+
+B<Note:> Parameters like C<index> or C<type> can be specified as C<index> or as
+C<_index>, so the following two lines are equivalent:
+
+ index => { index => 'index', type => 'type', id => 1, source => {...}},
+ index => { _index => 'index', _type => 'type', _id => 1, source => {...}},
+
+B<Note:> The C<index> and C<type> parameters can be specified in the
+params for any action, but if not specified, will default to the C<index>
+and C<type> values specified in L</new()>. These are required parameters:
+they must be specified either in L</new()> or in every action.
+
+=head2 C<create()>
+
+ $promise = $bulk->create(
+ { index => 'custom_index', source => { doc body }},
+ { type => 'custom_type', id => 1, source => { doc body }},
+ ...
+ );
+
+The C<create()> helper method allows you to add multiple C<create> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::7_0::Direct/create()>
+except that the document body should be passed as the C<source> or C<_source>
+parameter, instead of as C<body>.
+
+=head2 C<create_docs()>
+
+ $promise = $bulk->create_docs(
+ { doc body },
+ { doc body },
+ ...
+ );
+
+The C<create_docs()> helper is a shorter form of L</create()> which can be used
+when you are using the default C<index> and C<type> as set in L</new()>
+and you are not specifying a custom C<id> per document. In this case,
+you can just pass the individual document bodies.
+
+=head2 C<index()>
+
+ $promise = $bulk->index(
+ { index => 'custom_index', source => { doc body }},
+ { type => 'custom_type', id => 1, source => { doc body }},
+ ...
+ );
+
+The C<index()> helper method allows you to add multiple C<index> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::7_0::Direct/index()>
+except that the document body should be passed as the C<source> or C<_source>
+parameter, instead of as C<body>.
+
+=head2 C<delete()>
+
+ $promise = $bulk->delete(
+ { index => 'custom_index', id => 1},
+ { type => 'custom_type', id => 2},
+ ...
+ );
+
+The C<delete()> helper method allows you to add multiple C<delete> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::7_0::Direct/delete()>.
+
+=head2 C<delete_ids()>
+
+ $bulk->delete_ids(1,2,3...)
+
+The C<delete_ids()> helper method can be used when all of the documents you
+want to delete have the default C<index> and C<type> as set in L</new()>.
+In this case, all you have to do is to pass in a list of IDs.
+
+=head2 C<update()>
+
+ $promise = $bulk->update(
+ { id => 1,
+ doc => { partial doc },
+ doc_as_upsert => 1
+ },
+ { id => 2,
+ script => { script },
+ upsert => { upsert doc }
+ },
+ ...
+ );
+
+
+The C<update()> helper method allows you to add multiple C<update> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::7_0::Direct/update()>.
+An update can either use a I<partial doc> which gets merged with an existing
+doc (example 1 above), or can use a C<script> to update an existing doc
+(example 2 above). More information on C<script> can be found here:
+L<Search::Elasticsearch::Client::7_0::Direct/update()>.
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Async/Scroll.pm b/lib/Search/Elasticsearch/Client/7_0/Async/Scroll.pm
new file mode 100644
index 0000000..347bfe8
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Async/Scroll.pm
@@ -0,0 +1,528 @@
+# 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 Search::Elasticsearch::Client::7_0::Async::Scroll;
+
+use Moo;
+use Search::Elasticsearch::Util qw(parse_params throw);
+use Search::Elasticsearch::Async::Util qw(thenable);
+use Scalar::Util qw(weaken blessed);
+use Promises qw(deferred);
+use namespace::clean;
+
+has 'one_at_a_time' => ( is => 'ro' );
+has 'on_start' => ( is => 'ro', clearer => '_clear_on_start' );
+has 'on_results' => ( is => 'ro', clearer => '_clear_on_results' );
+has 'on_error' => ( is => 'lazy', clearer => '_clear_on_error' );
+has '_guard' => ( is => 'rwp', clearer => '_clear__guard' );
+
+with 'Search::Elasticsearch::Role::Is_Async',
+ 'Search::Elasticsearch::Client::7_0::Role::Scroll';
+
+#===================================
+sub BUILDARGS {
+#===================================
+ my ( $class, $search_params ) = parse_params(@_);
+
+ my %params;
+ for (qw(es on_start on_result on_results on_error)) {
+ my $val = delete $search_params->{$_};
+ next unless defined $val;
+ $params{$_} = $val;
+ }
+
+ $params{scroll} = $search_params->{scroll} ||= '1m';
+ $params{search_params} = $search_params;
+
+ if ( $params{on_result} ) {
+ $params{on_results} = delete $params{on_result};
+ $params{one_at_a_time} = 1;
+ }
+ elsif ( !$params{on_results} ) {
+ throw( 'Param', 'Missing required param: on_results or on_result' );
+ }
+ return \%params;
+}
+
+#===================================
+sub _build_on_error {
+#===================================
+ sub { warn "Scroll error: @_"; die @_ }
+}
+
+#===================================
+sub start {
+#===================================
+ my $self = shift;
+ $self->_set__guard($self);
+
+ $self->es->search( $self->search_params )->then(
+ sub {
+ $self->_first_results(@_);
+ }
+ )->then(
+ sub {
+ $self->_fetch_loop;
+ }
+ )->catch(
+ sub {
+ $self->on_error->(@_);
+ @_;
+ }
+ )->finally(
+ sub {
+ $self->finish;
+ $self->_clear__guard;
+ }
+ );
+}
+
+#===================================
+sub _first_results {
+#===================================
+ my ( $self, $results ) = @_;
+
+ my $total = $results->{hits}{total};
+ if (ref $total) {
+ $total = $total->{value};
+ }
+ $self->_set_total($total);
+ $self->_set_max_score( $results->{hits}{max_score} );
+ $self->_set_aggregations( $results->{aggregations} );
+ $self->_set_facets( $results->{facets} );
+ $self->_set_suggest( $results->{suggest} );
+ $self->_set_took( $results->{took} );
+ $self->_set_total_took( $results->{took} );
+
+ if ($total) {
+ $self->_set__scroll_id( $results->{_scroll_id} );
+ }
+ else {
+ $self->finish;
+ }
+
+ $self->on_start && $self->on_start->($self);
+
+ my $hits = $results->{hits}{hits};
+ return unless @$hits;
+ return $self->_push_results($hits);
+}
+
+#===================================
+sub _next_results {
+#===================================
+ my ( $self, $results ) = @_;
+ $self->_set__scroll_id( $results->{_scroll_id} );
+ $self->_set_total_took( $self->total_took + $results->{took} );
+
+ my $hits = $results->{hits}{hits};
+ return $self->finish
+ unless @$hits;
+ $self->_push_results($hits);
+}
+
+#===================================
+sub _fetch_loop {
+#===================================
+ my $self = shift;
+ my $d = deferred;
+
+ my $weak_loop;
+ my $loop = sub {
+ if ( $self->is_finished ) {
+ return $d->resolve;
+ }
+ $self->scroll_request->then( sub { $self->_next_results(@_) } )
+ ->done( $weak_loop, sub { $d->reject(@_) } );
+ };
+ weaken( $weak_loop = $loop );
+ $loop->();
+ return $d->promise;
+}
+
+#===================================
+sub _push_results {
+#===================================
+ my $self = shift;
+ my $it = $self->_results_iterator(@_);
+ my $on_results = $self->on_results;
+
+ my $deferred = deferred;
+
+ my $weak_process;
+ my $process = sub {
+ while ( !$self->is_finished ) {
+ my @results = $it->() or last;
+ my @response = $on_results->(@results);
+ my $promise = thenable(@response) or next;
+ return $promise->done( $weak_process,
+ sub { $deferred->reject(@_) } );
+ }
+ $deferred->resolve;
+ };
+ weaken( $weak_process = $process );
+ $process->();
+ return $deferred->promise;
+}
+
+#===================================
+sub _results_iterator {
+#===================================
+ my $self = shift;
+ my @results = @{ shift() };
+
+ $self->one_at_a_time
+ ? sub { splice @results, 0, 1 }
+ : sub { splice @results };
+}
+
+#===================================
+sub finish {
+#===================================
+ my $self = shift;
+ $self->_set_is_finished(1);
+
+ my $scroll_id = $self->_scroll_id;
+ $self->_clear_scroll_id;
+
+ if ( !$scroll_id || $self->_pid != $$ ) {
+ my $d = deferred;
+ $d->resolve();
+ return $d->promise;
+ }
+
+ my %args = ( body => { scroll_id => $scroll_id } );
+
+ $self->es->clear_scroll(%args)->then(
+ sub {
+ $self->_clear_on_start;
+ $self->_clear_on_results;
+ $self->_clear_on_error;
+ },
+ sub { }
+ );
+}
+
+1;
+
+# ABSTRACT: A helper module for scrolled searches
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new;
+
+ my $scroll = $es->scroll_helper
+ index => 'my_index',
+ body => {
+ size => 1000,
+ sort => '_doc',
+ query => {...}
+ },
+ on_start => \&on_start,
+ on_result => \&on_result,
+ | on_results => \&on_results,
+ on_error => \&on_error
+ );
+
+ $scroll->start->then( sub {say "Done"}, sub { warn @_ } );
+
+ sub on_start {
+ my $scroll = shift;
+ say "Total hits: ". $scroll->total;
+ }
+
+ sub on_result {
+ my $doc = shift;
+ do_something($doc);
+ }
+
+ sub on_results {
+ for my $doc (@_) {
+ do_something($doc)
+ }
+ }
+
+ sub on_error {
+ my $error = shift;
+ warn "$error";
+ }
+
+=head1 DESCRIPTION
+
+A I<scrolled search> is a search that allows you to keep pulling results
+until there are no more matching results, much like a cursor in an SQL
+database.
+
+Unlike paginating through results (with the C<from> parameter in
+L<search()|Search::Elasticsearch::Client::7_0::Direct/search()>),
+scrolled searches take a snapshot of the current state of the index. Even
+if you keep adding new documents to the index or updating existing documents,
+a scrolled search will only see the index as it was when the search began.
+
+This module is a helper utility that wraps the functionality of the
+L<search()|Search::Elasticsearch::Client::7_0::Direct/search()> and
+L<scroll()|Search::Elasticsearch::Client::7_0::Direct/scroll()> methods to make
+them easier to use.
+
+This class does L<Search::Elasticsearch::Client::7_0::Role::Scroll> and
+L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 USE CASES
+
+There are two primary use cases:
+
+=head2 Pulling enough results
+
+Perhaps you want to group your results by some field, and you don't know
+exactly how many results you will need in order to return 10 grouped
+results. With a scrolled search you can keep pulling more results
+until you have enough. For instance, you can search emails in a mailing
+list, and return results grouped by C<thread_id>:
+
+ use Promises qw(deferred);
+
+ sub find_email_threads {
+ my (%groups,@results,$scroll);
+
+ my $d = deferred;
+
+ $scroll = $es->scroll_helper(
+ index => 'my_emails',
+ type => 'email',
+ body => { query => {... some query ... }},
+ on_result => sub {
+ my $doc = shift;
+ my $thread = $doc->{_source}{thread_id};
+ unless ($groups{$thread}) {
+ $groups{$thread} = [];
+ push @results, $groups{$thread};
+ }
+ push @{$groups{$thread}},$doc;
+
+ # stop collecting if we have 10 results
+ if (@results == 10) {
+ $scroll->finish;
+ }
+ }
+ );
+
+ $scroll->start->then(
+ # resolve with results if completed successfully
+ sub { $d->resolve(@results) },
+
+ # reject with error if failed
+ sub { $d->reject(@_) }
+ );
+
+ return $d->promise;
+ }
+
+=head2 Extracting all documents
+
+Often you will want to extract all (or a subset of) documents in an index.
+If you want to change your type mappings, you will need to reindex all of your
+data. Or perhaps you want to move a subset of the data in one index into
+a new dedicated index. In these cases, you don't care about sort
+order, you just want to retrieve all documents which match a query, and do
+something with them. For instance, to retrieve all the docs for a particular
+C<client_id>:
+
+ $es->scroll_helper(
+ index => 'my_index',
+ size => 1000,
+ body => {
+ query => {
+ match => {
+ client_id => 123
+ }
+ },
+ sort => '_doc'
+ },
+ on_result => sub { do_something(@_) }
+ )->start;
+
+Very often the I<something> that you will want to do with these results
+involves bulk-indexing them into a new index. The easiest way to
+do this is to use the built-in L<Search::Elasticsearch::Client::7_0::Direct/reindex()>
+functionality provided by Elasticsearch.
+
+=head1 METHODS
+
+=head2 C<new()>
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new(...);
+ my $scroll = $es->scroll_helper(
+ scroll => '1m', # optional
+
+ on_result => sub {...} # required
+ | on_results => sub {...} # required
+
+ on_start => sub {...} # optional
+ on_error => sub {...} # optional
+ %search_params,
+ );
+ $scroll->start;
+
+The L<Search::Elasticsearch::Client::7_0::Direct/scroll_helper()> method loads
+L<Search::Elasticsearch::Client::7_0::Async::Scroll> class and calls L</new()>,
+passing in any arguments.
+
+You can specify a C<scroll> duration (which defaults to C<"1m">).
+Any other parameters are passed directly to L<Search::Elasticsearch::Client::7_0::Direct/search()>.
+
+The C<scroll> duration tells Elasticearch how long it should keep the scroll
+alive. B<Note>: this duration doesn't need to be long enough to process
+all results, just long enough to process a single B<batch> of results.
+The expiry gets renewed for another C<scroll> period every time new
+a new batch of results is retrieved from the cluster.
+
+By default, the C<scroll_id> is passed as the C<body> to the
+L<scroll|Search::Elasticsearch::Client::7_0::Direct/scroll()> request.
+
+The C<scroll> request uses C<GET> by default. To use C<POST> instead,
+set L<send_get_body_as|Search::Elasticsearch::Transport/send_get_body_as> to
+C<POST>.
+
+=head3 Callbacks
+
+You must specify either an C<on_result> callback or an C<on_results> callback.
+
+=head4 C<on_result> and C<on_results>
+
+The C<on_result> callback is called once for every result that is received.
+
+ sub on_result {
+ my $doc = shift;
+ do_something($doc);
+ }
+
+Alternatively, you can specify an C<on_results> callback which is called
+once for every set of results returned by Elasticsearch:
+
+ sub on_results {
+ for my $doc (@_) {
+ do_something($doc)
+ }
+ }
+
+If either C<on_result> or C<on_results> returns a new L<Promise>, processing
+of further results will be paused until the promise has been rejected or
+resolved.
+
+=head4 C<on_start>
+
+The C<on_start> callback is called after the first request has completed,
+at which stage the properties like C<total()>, C<aggregations()>, etc
+will have been populated.
+
+=head4 C<on_error>
+
+The C<on_error> callback is called if any error occurs. The default
+implementation warns about the error, and rethrows it.
+
+ sub on_error { warn "Scroll error: @_"; die @_ }
+
+If you wish to handle (and surpress) certain errors, then don't call C<die()>,
+eg:
+
+ sub on_error {
+ my $error = shift;
+ if ($error =~/SomeCatchableError/) {
+ # do something to handle error
+ }
+ else {
+ # rethrow error
+ die $error;
+ }
+ }
+
+=head2 C<start()>
+
+ $scroll->start
+ ->then( \&success, \&failure );
+
+The C<start()> method starts the scroll and returns a L<Promise> which
+will be resolved when the scroll completes (or L</finish()> is called),
+or rejected if any errors remain unhandled.
+
+=head2 C<finish()>
+
+ $scroll->finish;
+
+The C<finish()> method clears out the buffer, sets L</is_finished()> to C<true>
+and tries to clear the C<scroll_id> on Elasticsearch. This API is only
+supported since v0.90.6, but the call to C<clear_scroll> is wrapped in an
+C<eval> so the C<finish()> method can be safely called with any version
+of Elasticsearch.
+
+When the C<$scroll> instance goes out of scope, L</finish()> is called
+automatically if required.
+
+=head2 C<is_finished()>
+
+ $bool = $scroll->is_finished;
+
+A flag which returns C<true> if all results have been processed or
+L</finish()> has been called.
+
+=head1 INFO ACCESSORS
+
+The information from the original search is returned via the accessors
+below. These values can be accessed in the C<on_start> callback:
+
+=head2 C<total>
+
+The total number of documents that matched your query.
+
+=head2 C<max_score>
+
+The maximum score of any documents in your query.
+
+=head2 C<aggregations>
+
+Any aggregations that were specified, or C<undef>
+
+=head2 C<facets>
+
+Any facets that were specified, or C<undef>
+
+=head2 C<suggest>
+
+Any suggestions that were specified, or C<undef>
+
+=head2 C<took>
+
+How long the original search took, in milliseconds
+
+=head2 C<took_total>
+
+How long the original search plus all subsequent batches took, in milliseconds.
+This value can only be checked once the scroll has completed.
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Client::7_0::Direct/search()>
+
+=item * L<Search::Elasticsearch::Client::7_0::Direct/scroll()>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/2_0/Bulk.pm b/lib/Search/Elasticsearch/Client/7_0/Bulk.pm
similarity index 59%
rename from lib/Search/Elasticsearch/Client/2_0/Bulk.pm
rename to lib/Search/Elasticsearch/Client/7_0/Bulk.pm
index c1b409d..134f973 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Bulk.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Bulk.pm
@@ -1,7 +1,24 @@
-package Search::Elasticsearch::Client::2_0::Bulk;
-$Search::Elasticsearch::Client::2_0::Bulk::VERSION = '6.81';
+# 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 Search::Elasticsearch::Client::7_0::Bulk;
+
use Moo;
-with 'Search::Elasticsearch::Client::2_0::Role::Bulk',
+with 'Search::Elasticsearch::Client::7_0::Role::Bulk',
'Search::Elasticsearch::Role::Is_Sync';
use Search::Elasticsearch::Util qw(parse_params throw);
use Try::Tiny;
@@ -65,53 +82,11 @@ sub flush {
return defined wantarray ? $results : undef;
}
-#===================================
-sub reindex {
-#===================================
- my ( $self, $params ) = parse_params(@_);
- my $src = $params->{source}
- or throw( 'Param', "Missing required param <source>" );
-
- my $transform = $self->_doc_transformer($params);
-
- if ( ref $src eq 'HASH' ) {
- $src = {%$src};
- my $es = delete $src->{es} || $self->es;
- my $scroll = $es->scroll_helper(
- search_type => 'scan',
- size => 500,
- %$src
- );
-
- $src = sub {
- $scroll->refill_buffer;
- $scroll->drain_buffer;
- };
-
- print "Reindexing " . $scroll->total . " docs\n"
- if $self->verbose;
- }
-
- while ( my @docs = grep {defined} $src->() ) {
- $self->index( grep {$_} map { $transform->($_) } @docs );
- }
- $self->flush;
- return 1;
-}
-
1;
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Bulk - A helper module for the Bulk API and for reindexing
-
-=head1 VERSION
+__END__
-version 6.81
+# ABSTRACT: A helper module for the Bulk API
=head1 SYNOPSIS
@@ -144,29 +119,20 @@ version 6.81
# Manual flush
$bulk->flush;
- # Reindex docs:
- my $bulk = $es->bulk_helper(
- index => 'new_index',
- verbose => 1
- );
-
- $bulk->reindex( source => { index => 'old_index' });
-
=head1 DESCRIPTION
-This module provides a wrapper for the L<Search::Elasticsearch::Client::2_0::Direct/bulk()>
+This module provides a wrapper for the L<Search::Elasticsearch::Client::7_0::Direct/bulk()>
method which makes it easier to run multiple create, index, update or delete
-actions in a single request. It also provides a simple interface
-for L<reindexing documents|/REINDEXING DOCUMENTS>.
+actions in a single request.
-The L<Search::Elasticsearch::Client::2_0::Bulk> module acts as a queue, buffering up actions
+The L<Search::Elasticsearch::Client::7_0::Bulk> module acts as a queue, buffering up actions
until it reaches a maximum count of actions, or a maximum size of JSON request
body, at which point it issues a C<bulk()> request.
Once you have finished adding actions, call L</flush()> to force the final
C<bulk()> request on the items left in the queue.
-This class does L<Search::Elasticsearch::Client::2_0::Role::Bulk> and
+This class does L<Search::Elasticsearch::Client::7_0::Role::Bulk> and
L<Search::Elasticsearch::Role::Is_Sync>.
=head1 CREATING A NEW INSTANCE
@@ -181,7 +147,7 @@ L<Search::Elasticsearch::Role::Is_Sync>.
max_count => 1_000, # optional
max_size => 1_000_000, # optional
- max_time => 5, # optional
+ max_time => 6, # optional
verbose => 0 | 1, # optional
@@ -198,7 +164,7 @@ Search::Elasticsearch client as the C<es> argument.
The C<index> and C<type> parameters provide default values for
C<index> and C<type>, which can be overridden in each action.
You can also pass any other values which are accepted
-by the L<bulk()|Search::Elasticsearch::Client::2_0::Direct/bulk()> method.
+by the L<bulk()|Search::Elasticsearch::Client::7_0::Direct/bulk()> method.
See L</flush()> for more information about the other parameters.
@@ -209,7 +175,7 @@ See L</flush()> for more information about the other parameters.
$result = $bulk->flush;
The C<flush()> method sends all buffered actions to Elasticsearch using
-a L<bulk()|Search::Elasticsearch::Client::2_0::Direct/bulk()> request.
+a L<bulk()|Search::Elasticsearch::Client::7_0::Direct/bulk()> request.
=head2 Auto-flushing
@@ -356,8 +322,8 @@ See the individual helper methods below for details.
B<Note:> Parameters like C<index> or C<type> can be specified as C<index> or as
C<_index>, so the following two lines are equivalent:
- index => { index => 'index', type => 'type', id => 1, source => {...}},
- index => { _index => 'index', _type => 'type', _id => 1, _source => {...}},
+ index => { index => 'index', type => 'type', id => 1, source => {...}},
+ index => { _index => 'index', _type => 'type', _id => 1, source => {...}},
B<Note:> The C<index> and C<type> parameters can be specified in the
params for any action, but if not specified, will default to the C<index>
@@ -373,7 +339,7 @@ they must be specified either in L</new()> or in every action.
);
The C<create()> helper method allows you to add multiple C<create> actions.
-It accepts the same parameters as L<Search::Elasticsearch::Client::2_0::Direct/create()>
+It accepts the same parameters as L<Search::Elasticsearch::Client::7_0::Direct/create()>
except that the document body should be passed as the C<source> or C<_source>
parameter, instead of as C<body>.
@@ -399,7 +365,7 @@ you can just pass the individual document bodies.
);
The C<index()> helper method allows you to add multiple C<index> actions.
-It accepts the same parameters as L<Search::Elasticsearch::Client::2_0::Direct/index()>
+It accepts the same parameters as L<Search::Elasticsearch::Client::7_0::Direct/index()>
except that the document body should be passed as the C<source> or C<_source>
parameter, instead of as C<body>.
@@ -412,7 +378,7 @@ parameter, instead of as C<body>.
);
The C<delete()> helper method allows you to add multiple C<delete> actions.
-It accepts the same parameters as L<Search::Elasticsearch::Client::2_0::Direct/delete()>.
+It accepts the same parameters as L<Search::Elasticsearch::Client::7_0::Direct/delete()>.
=head2 C<delete_ids()>
@@ -430,185 +396,17 @@ In this case, all you have to do is to pass in a list of IDs.
doc_as_upsert => 1
},
{ id => 2,
- lang => 'mvel',
script => { script }
upsert => { upsert doc }
},
...
);
+
The C<update()> helper method allows you to add multiple C<update> actions.
-It accepts the same parameters as L<Search::Elasticsearch::Client::2_0::Direct/update()>.
+It accepts the same parameters as L<Search::Elasticsearch::Client::7_0::Direct/update()>.
An update can either use a I<partial doc> which gets merged with an existing
doc (example 1 above), or can use a C<script> to update an existing doc
(example 2 above). More information on C<script> can be found here:
-L<Search::Elasticsearch::Client::2_0::Direct/update()>.
-
-=head1 REINDEXING DOCUMENTS
-
-A common use case for bulk indexing is to reindex a whole index when
-changing the type mappings or analysis chain. This typically
-combines bulk indexing with L<scrolled searches|Search::Elasticsearch::Client::2_0::Scroll>:
-the scrolled search pulls all of the data from the source index, and
-the bulk indexer indexes the data into the new index.
-
-=head2 C<reindex()>
-
- $bulk->reindex(
- source => $source, # required
- transform => \&transform, # optional
- version_type => 'external|internal', # optional
- );
-
-The C<reindex()> method requires a C<$source> parameter, which provides
-the source for the documents which are to be reindexed.
-
-=head2 Reindexing from another index
-
-If the C<source> argument is a HASH ref, then the hash is passed to
-L<Search::Elasticsearch::Client::2_0::Scroll/new()> to create a new scrolled search.
-
- my $bulk = $es->bulk_helper(
- index => 'new_index',
- verbose => 1
- );
-
- $bulk->reindex(
- source => {
- index => 'old_index',
- size => 500, # default
- search_type => 'scan' # default
- }
- );
-
-If a default C<index> or C<type> has been specified in the call to
-L</new()>, then it will replace the C<index> and C<type> values for
-the docs returned from the scrolled search. In the example above,
-all docs will be retrieved from C<"old_index"> and will be bulk indexed
-into C<"new_index">.
-
-=head2 Reindexing from a generic source
-
-The C<source> parameter also accepts a coderef or an anonymous sub,
-which should return one or more new documents every time it is executed.
-This allows you to pass any iterator, wrapped in an anonymous sub:
-
- my $iter = get_iterator_from_somewhere();
-
- $bulk->reindex(
- source => sub { $iter->next }
- );
-
-=head2 Transforming docs on the fly
-
-The C<transform> parameter allows you to change documents on the fly,
-using a callback. The callback receives the document as the only argument,
-and should return the updated document, or C<undef> if the document should
-not be indexed:
-
- $bulk->reindex(
- source => { index => 'old_index' },
- transform => sub {
- my $doc = shift;
-
- # don't index doc marked as valid:false
- return undef unless $doc->{_source}{valid};
-
- # convert $tag to @tags
- $doc->{_source}{tags} = [ delete $doc->{_source}{tag}];
- return $doc
- }
- );
-
-=head2 Reindexing from another cluster
-
-By default, L</reindex()> expects the source and destination indices
-to be in the same cluster. To pull data from one cluster and index it into
-another, you can use two separate C<$es> objects:
-
- $es_target = Search::Elasticsearch->new( nodes => 'localhost:9200' );
- $es_source = Search::Elasticsearch->new( nodes => 'search1:9200' );
-
- my $bulk = $es_targert->bulk_helper(
- verbose => 1
- )
- -> reindex(
- source => {
- es => $es_source,
- index => 'my_index'
- }
- );
-
-=head2 Parents and routing
-
-If you are using parent-child relationships or custom C<routing> values,
-and you want to preserve these when you reindex your documents, then
-you will need to request these values specifically, as follows:
-
- $bulk->reindex(
- source => {
- index => 'old_index',
- fields => ['_source','_parent','_routing']
- }
- );
-
-=head2 Working with version numbers
-
-Every document in Elasticsearch has a current C<version> number, which
-is used for L<optimistic concurrency control|http://en.wikipedia.org/wiki/Optimistic_concurrency_control>,
-that is, to ensure that you don't overwrite changes that have been made
-by another process.
-
-All CRUD operations accept a C<version> parameter and a C<version_type>
-parameter which tells Elasticsearch that the change should only be made
-if the current document corresponds to these parameters. The
-C<version_type> parameter can have the following values:
-
-=over
-
-=item * C<internal>
-
-Use Elasticsearch version numbers. Documents are only changed if the
-document in Elasticsearch has the B<same> C<version> number that is
-specified in the CRUD operation. After the change, the new
-version number is C<version+1>.
-
-=item * C<external>
-
-Use an external versioning system, such as timestamps or version numbers
-from an external database. Documents are only changed if the document
-in Elasticsearch has a B<lower> C<version> number than the one
-specified in the CRUD operation. After the change, the new version
-number is C<version>.
-
-=back
-
-If you would like to reindex documents from one index to another, preserving
-the C<version> numbers from the original index, then you need the following:
-
- $bulk->reindex(
- source => {
- index => 'old_index',
- version => 1, # retrieve version numbers in search
- },
- version_type => 'external' # use these "external" version numbers
- );
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
-
-# ABSTRACT: A helper module for the Bulk API and for reindexing
+L<Search::Elasticsearch::Client::7_0::Direct/update()>.
diff --git a/lib/Search/Elasticsearch/Client/2_0/Direct.pm b/lib/Search/Elasticsearch/Client/7_0/Direct.pm
similarity index 66%
rename from lib/Search/Elasticsearch/Client/2_0/Direct.pm
rename to lib/Search/Elasticsearch/Client/7_0/Direct.pm
index 7ca3ffd..4a1ea5e 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Direct.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct.pm
@@ -1,36 +1,51 @@
-package Search::Elasticsearch::Client::2_0::Direct;
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Client::7_0::Direct;
use Moo;
-with 'Search::Elasticsearch::Client::2_0::Role::API';
+with 'Search::Elasticsearch::Client::7_0::Role::API';
with 'Search::Elasticsearch::Role::Client::Direct';
-our $VERSION='6.81';
-use Search::Elasticsearch 6.00 ();
-
use Search::Elasticsearch::Util qw(parse_params is_compat);
use namespace::clean;
sub _namespace {__PACKAGE__}
-has 'cluster' => ( is => 'lazy', init_arg => undef );
-has 'nodes' => ( is => 'lazy', init_arg => undef );
-has 'indices' => ( is => 'lazy', init_arg => undef );
-has 'snapshot' => ( is => 'lazy', init_arg => undef );
-has 'cat' => ( is => 'lazy', init_arg => undef );
-has 'tasks' => ( is => 'lazy', init_arg => undef );
-has 'bulk_helper_class' => ( is => 'rw' );
-has 'scroll_helper_class' => ( is => 'rw' );
-has '_bulk_class' => ( is => 'lazy' );
-has '_scroll_class' => ( is => 'lazy' );
-
-#===================================
-sub create {
-#===================================
- my ( $self, $params ) = parse_params(@_);
- my $defn = $self->api->{index};
- $params->{op_type} = 'create';
- $self->perform_request( { %$defn, name => 'create' }, $params );
-}
+has 'async_search' => ( is => 'lazy', init_arg => undef );
+has 'autoscaling' => ( is => 'lazy', init_arg => undef );
+has 'cat' => ( is => 'lazy', init_arg => undef );
+has 'ccr' => ( is => 'lazy', init_arg => undef );
+has 'cluster' => ( is => 'lazy', init_arg => undef );
+has 'dangling_indices' => ( is => 'lazy', init_arg => undef );
+has 'data_frame_transform_deprecated' => ( is => 'lazy', init_arg => undef );
+has 'enrich' => ( is => 'lazy', init_arg => undef );
+has 'eql' => ( is => 'lazy', init_arg => undef );
+has 'graph' => ( is => 'lazy', init_arg => undef );
+has 'ilm' => ( is => 'lazy', init_arg => undef );
+has 'indices' => ( is => 'lazy', init_arg => undef );
+has 'ingest' => ( is => 'lazy', init_arg => undef );
+has 'license' => ( is => 'lazy', init_arg => undef );
+has 'migration' => ( is => 'lazy', init_arg => undef );
+has 'ml' => ( is => 'lazy', init_arg => undef );
+has 'monitoring' => ( is => 'lazy', init_arg => undef );
+has 'nodes' => ( is => 'lazy', init_arg => undef );
+has 'rollup' => ( is => 'lazy', init_arg => undef );
+has 'searchable_snapshots' => ( is => 'lazy', init_arg => undef );
+has 'security' => ( is => 'lazy', init_arg => undef );
+has 'snapshot' => ( is => 'lazy', init_arg => undef );
+has 'slm' => ( is => 'lazy', init_arg => undef );
+has 'sql' => ( is => 'lazy', init_arg => undef );
+has 'ssl' => ( is => 'lazy', init_arg => undef );
+has 'tasks' => ( is => 'lazy', init_arg => undef );
+has 'transform' => ( is => 'lazy', init_arg => undef );
+has 'watcher' => ( is => 'lazy', init_arg => undef );
+has 'xpack' => ( is => 'lazy', init_arg => undef );
+has 'bulk_helper_class' => ( is => 'rw' );
+has 'scroll_helper_class' => ( is => 'rw' );
+has '_bulk_class' => ( is => 'lazy' );
+has '_scroll_class' => ( is => 'lazy' );
#===================================
sub _build__bulk_class {
@@ -67,29 +82,44 @@ sub scroll_helper {
}
#===================================
-sub _build_cluster { shift->_build_namespace('Cluster') }
-sub _build_nodes { shift->_build_namespace('Nodes') }
-sub _build_indices { shift->_build_namespace('Indices') }
-sub _build_snapshot { shift->_build_namespace('Snapshot') }
-sub _build_cat { shift->_build_namespace('Cat') }
-sub _build_tasks { shift->_build_namespace('Tasks') }
+sub _build_autoscaling { shift->_build_namespace('Autoscaling') }
+sub _build_async_search { shift->_build_namespace('AsyncSearch') }
+sub _build_cat { shift->_build_namespace('Cat') }
+sub _build_ccr { shift->_build_namespace('CCR') }
+sub _build_cluster { shift->_build_namespace('Cluster') }
+sub _build_dangling_indices { shift->_build_namespace('DanglingIndices') }
+sub _build_data_frame_transform_deprecated { shift->_build_namespace('DataFrameTransformDeprecated') }
+sub _build_enrich { shift->_build_namespace('Enrich') }
+sub _build_eql { shift->_build_namespace('Eql') }
+sub _build_graph { shift->_build_namespace('Graph') }
+sub _build_ilm { shift->_build_namespace('ILM') }
+sub _build_indices { shift->_build_namespace('Indices') }
+sub _build_ingest { shift->_build_namespace('Ingest') }
+sub _build_license { shift->_build_namespace('License') }
+sub _build_migration { shift->_build_namespace('Migration') }
+sub _build_ml { shift->_build_namespace('ML') }
+sub _build_monitoring { shift->_build_namespace('Monitoring') }
+sub _build_nodes { shift->_build_namespace('Nodes') }
+sub _build_rollup { shift->_build_namespace('Rollup') }
+sub _build_searchable_snapshots { shift->_build_namespace('SearchableSnapshots') }
+sub _build_security { shift->_build_namespace('Security') }
+sub _build_snapshot { shift->_build_namespace('Snapshot') }
+sub _build_slm { shift->_build_namespace('Slm') }
+sub _build_sql { shift->_build_namespace('SQL') }
+sub _build_ssl { shift->_build_namespace('SSL') }
+sub _build_tasks { shift->_build_namespace('Tasks') }
+sub _build_transform { shift->_build_namespace('Transform') }
+sub _build_watcher { shift->_build_namespace('Watcher') }
+sub _build_xpack { shift->_build_namespace('XPack') }
#===================================
__PACKAGE__->_install_api('');
1;
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Direct - Thin client with full support for Elasticsearch 2.x APIs
-
-=head1 VERSION
+__END__
-version 6.81
+# ABSTRACT: Thin client with full support for Elasticsearch 7.x APIs
=head1 SYNOPSIS
@@ -97,7 +127,7 @@ Create a client:
use Search::Elasticsearch;
my $e = Search::Elasticsearch->new(
- client => '2_0::Direct'
+ client => '7_0::Direct'
);
Index a doc:
@@ -139,6 +169,10 @@ Index-level requests:
$e->indices->create( index => 'my_index' );
$e->indices->delete( index => 'my_index' )
+Ingest pipeline requests:
+
+ $e->ingest->get_pipeline( id => 'apache-logs' );
+
Cluster-level requests:
$health = $e->cluster->health;
@@ -172,13 +206,21 @@ Task management:
say $e->cat->allocation;
say $e->cat->health;
+Cross-cluster replication requests:
+
+ say $e->ccr->follow;
+
+Index lifecycle management requests:
+
+ say $e->ilm->put_lifecycle;
+
=head1 DESCRIPTION
-The L<Search::Elasticsearch::Client::2_0::Direct> class provides the
-Elasticsearch 2.x compatible client returned by:
+The L<Search::Elasticsearch::Client::7_0::Direct> class provides the
+Elasticsearch 7.x compatible client returned by:
$e = Search::Elasticsearch->new(
- client => "2_0::Direct"
+ client => "7_0::Direct" # default
);
It is intended to be as close as possible to the native REST API that
@@ -191,6 +233,38 @@ L<bulk document CRUD|/BULK DOCUMENT CRUD METHODS> and L<search|/SEARCH METHODS>.
It also provides access to clients for managing L<indices|/indices()>
and the L<cluster|/cluster()>.
+=head1 PREVIOUS VERSIONS OF ELASTICSEARCH
+
+This version of the client supports the Elasticsearch 7.0 branch,
+which is not backwards compatible with earlier branches.
+
+If you need to talk to a version of Elasticsearch before 7.0.0, please
+install one of the following modules:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Client::6_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::5_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::2_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::1_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::0_90>
+
+=back
+
=head1 CONVENTIONS
=head2 Parameter passing
@@ -222,11 +296,11 @@ as top level parameters:
If you pass in a C<\%params> hash, then it will be included in the
query string parameters without any error checking. The following:
- $e->search( size => 10, params => { from => 5, size => 5 })
+ $e->search( size => 10, params => { from => 6, size => 6 })
would result in this query string:
- ?from=5&size=10
+ ?from=6&size=10
=head2 Body parameter
@@ -243,6 +317,21 @@ UTF-8 bytes and passed as is:
$e->indices->analyze( body => "The quick brown fox");
+=head2 Boolean parameters
+
+Elasticsearch 7.0.0 and above no longer accepts truthy and falsey values for booleans. Instead,
+it will accept only a JSON C<true> or C<false>, or the string equivalents C<"true"> or C<"false">.
+
+In the Perl client, you can use the following values:
+
+=over
+
+=item * True: C<true>, C<\1>, or a L<JSON::PP::Boolean> object.
+
+=item * False: C<false>, C<\0>, or a L<JSON::PP::Boolean> object.
+
+=back
+
=head2 Filter path parameter
Any API which returns a JSON body accepts a C<filter_path> parameter
@@ -253,7 +342,7 @@ you can do:
$e->search(
query => {...},
- filter_path => [ 'hits.total', 'hits.hits._source' ]
+ filter_paths => [ 'hits.total', 'hits.hits._source' ]
);
=head2 Ignore parameter
@@ -284,12 +373,12 @@ Multiple error codes can be specified with an array:
=head2 C<bulk_helper_class>
The class to use for the L</bulk_helper()> method. Defaults to
-L<Search::Elasticsearch::Client::2_0::Bulk>.
+L<Search::Elasticsearch::Client::7_0::Bulk>.
=head2 C<scroll_helper_class>
The class to use for the L</scroll_helper()> method. Defaults to
-L<Search::Elasticsearch::Client::2_0::Scroll>.
+L<Search::Elasticsearch::Client::7_0::Scroll>.
=head1 GENERAL METHODS
@@ -311,29 +400,36 @@ response, otherwise it throws an error.
$indices_client = $e->indices;
-Returns an L<Search::Elasticsearch::Client::2_0::Direct::Indices> object which can be used
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::Indices> object which can be used
for managing indices, eg creating, deleting indices, managing mapping,
index settings etc.
+=head2 C<ingest()>
+
+ $ingest_client = $e->ingest;
+
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::Ingest> object which can be used
+for managing ingest pipelines.
+
=head2 C<cluster()>
$cluster_client = $e->cluster;
-Returns an L<Search::Elasticsearch::Client::2_0::Direct::Cluster> object which can be used
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::Cluster> object which can be used
for managing the cluster, eg cluster-wide settings and cluster health.
=head2 C<nodes()>
$node_client = $e->nodes;
-Returns an L<Search::Elasticsearch::Client::2_0::Direct::Nodes> object which can be used
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::Nodes> object which can be used
to retrieve node info and stats.
=head2 C<snapshot()>
$snapshot_client = $e->snapshot;
-Returns an L<Search::Elasticsearch::Client::2_0::Direct::Snapshot> object which
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::Snapshot> object which
is used for managing backup repositories and creating and restoring
snapshots.
@@ -341,17 +437,32 @@ snapshots.
$tasks_client = $e->tasks;
-Returns an L<Search::Elasticsearch::Client::2_0::Direct::Tasks> object which
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::Tasks> object which
is used for accessing the task management API.
=head2 C<cat()>
$cat_client = $e->cat;
-Returns an L<Search::Elasticsearch::Client::2_0::Direct::Cat> object which can be used
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::Cat> object which can be used
to retrieve simple to read text info for debugging and monitoring an
Elasticsearch cluster.
+=head2 C<ccr()>
+
+ $ccr_client = $e->ccr;
+
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::CCR> object which can be used
+to handle cross-cluster replication requests.
+
+
+=head2 C<ilm()>
+
+ $ilm_client = $e->ilm;
+
+Returns a L<Search::Elasticsearch::Client::7_0::Direct::ILM> object which can be used
+to handle index lifecycle management requests.
+
=head1 DOCUMENT CRUD METHODS
These methods allow you to perform create, index, update and delete requests
@@ -371,16 +482,19 @@ The C<index()> method is used to index a new document or to reindex
an existing document.
Query string parameters:
- C<consistency>,
+ C<error_trace>,
+ C<human>,
+ C<if_primary_term>,
+ C<if_seq_no>,
C<op_type>,
C<parent>,
+ C<pipeline>,
C<refresh>,
C<routing>,
C<timeout>,
- C<timestamp>,
- C<ttl>,
C<version>,
- C<version_type>
+ C<version_type>,
+ C<wait_for_active_shards>
See the L<index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html>
for more information.
@@ -390,7 +504,7 @@ for more information.
$response = $e->create(
index => 'index_name', # required
type => 'type_name', # required
- id => 'doc_id', # optional, otherwise auto-generated
+ id => 'doc_id', # required
body => { document } # required
);
@@ -401,13 +515,13 @@ C<index>, C<type> and C<id> already exists.
Query string parameters:
C<consistency>,
+ C<error_trace>,
+ C<human>,
C<op_type>,
C<parent>,
C<refresh>,
C<routing>,
C<timeout>,
- C<timestamp>,
- C<ttl>,
C<version>,
C<version_type>
@@ -427,14 +541,16 @@ C<index>, C<type> and C<id>, or will throw a C<Missing> error.
Query string parameters:
C<_source>,
- C<_source_exclude>,
- C<_source_include>,
- C<fields>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<human>,
C<parent>,
C<preference>,
C<realtime>,
C<refresh>,
C<routing>,
+ C<stored_fields>,
C<version>,
C<version_type>
@@ -456,8 +572,10 @@ plus the document metadata, ie the C<_index>, C<_type> etc.
Query string parameters:
C<_source>,
- C<_source_exclude>,
- C<_source_include>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<human>,
C<parent>,
C<preference>,
C<realtime>,
@@ -481,11 +599,18 @@ The C<exists()> method returns C<1> if a document with the specified
C<index>, C<type> and C<id> exists, or an empty string if it doesn't.
Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<human>,
C<parent>,
C<preference>,
C<realtime>,
C<refresh>,
- C<routing>
+ C<routing>,
+ C<version>,
+ C<version_type>
See the L<exists docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html>
for more information.
@@ -502,13 +627,17 @@ The C<delete()> method will delete the document with the specified
C<index>, C<type> and C<id>, or will throw a C<Missing> error.
Query string parameters:
- C<consistency>,
+ C<error_trace>,
+ C<human>,
+ C<if_primary_term>,
+ C<if_seq_no>,
C<parent>,
C<refresh>,
C<routing>,
C<timeout>,
C<version>,
- C<version_type>
+ C<version_type>,
+ C<wait_for_active_shards>
See the L<delete docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html>
for more information.
@@ -543,16 +672,12 @@ C<index>, C<type> and C<id> if it exists. Updates can be performed either by:
...,
body => {
script => {
- inline => "ctx._source.counter += incr",
- params => { incr => 5 }
+ source => "ctx._source.counter += incr",
+ params => { incr => 6 }
}
}
);
-Make sure you enable
-L<dynamic scripting|https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#enable-dynamic-scripting>
-and know its implications.
-
=item * with an indexed script:
$response = $e->update(
@@ -560,7 +685,8 @@ and know its implications.
body => {
script => {
id => $id,
- params => { incr => 5 }
+ lang => 'painless',
+ params => { incr => 6 }
}
}
);
@@ -575,8 +701,8 @@ for more information.
body => {
script => {
file => 'counter',
- lang => 'groovy',
- params => { incr => 5 }
+ lang => 'painless',
+ params => { incr => 6 }
}
}
);
@@ -587,22 +713,23 @@ for more information.
=back
Query string parameters:
- C<consistency>,
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
C<fields>,
+ C<human>,
+ C<if_primary_term>,
+ C<if_seq_no>,
C<lang>,
C<parent>,
- C<realtime>,
C<refresh>,
C<retry_on_conflict>,
C<routing>,
- C<script>,
- C<script_id>,
- C<scripted_upsert>,
C<timeout>,
- C<timestamp>,
- C<ttl>,
C<version>,
- C<version_type>
+ C<version_type>,
+ C<wait_for_active_shards>
See the L<update docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html>
for more information.
@@ -622,9 +749,10 @@ offsets and payloads for the specified document, assuming that termvectors
have been enabled.
Query string parameters:
- C<dfs>,
+ C<error_trace>,
C<field_statistics>,
C<fields>,
+ C<human>,
C<offsets>,
C<parent>,
C<payloads>,
@@ -654,7 +782,7 @@ that need to be made, bulk requests greatly improve performance.
body => [ actions ] # required
);
-See L<Search::Elasticsearch::Client::2_0::Bulk> and L</bulk_helper()> for a helper module that makes
+See L<Search::Elasticsearch::Client::7_0::Bulk> and L</bulk_helper()> for a helper module that makes
bulk indexing simpler to use.
The C<bulk()> method can perform multiple L</index()>, L</create()>,
@@ -678,6 +806,7 @@ eg:
{ update => { _index => 'index', _type => 'type', _id => 123 }},
{ script => "ctx._source.counter+=1" }
+
Each action can include the same parameters that you would pass to
the equivalent L</index()>, L</create()>, L</delete()> or L</update()>
request, except that C<_index>, C<_type> and C<_id> must be specified with
@@ -704,7 +833,7 @@ For instance:
{ title => 'Foo' },
# delete action
- { delete => { _id => 125 }},
+ { delete => { _id => 126 }},
# update action
{ update => { _id => 126 }},
@@ -716,11 +845,17 @@ Each action is performed separately. One failed action will not
cause the others to fail as well.
Query string parameters:
- C<consistency>,
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
C<fields>,
+ C<human>,
+ C<pipeline>,
C<refresh>,
C<routing>,
- C<timeout>
+ C<timeout>,
+ C<wait_for_active_shards>
See the L<bulk docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html>
for more information.
@@ -730,7 +865,7 @@ for more information.
$bulk_helper = $e->bulk_helper( @args );
Returns a new instance of the class specified in the L</bulk_helper_class>,
-which defaults to L<Search::Elasticsearch::Client::2_0::Bulk>.
+which defaults to L<Search::Elasticsearch::Client::7_0::Bulk>.
=head2 C<mget()>
@@ -771,12 +906,15 @@ C<ids> of the documents to retrieve:
Query string parameters:
C<_source>,
- C<_source_exclude>,
- C<_source_include>,
- C<fields>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<human>,
C<preference>,
C<realtime>,
- C<refresh>
+ C<refresh>,
+ C<routing>,
+ C<stored_fields>
See the L<mget docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html>
for more information.
@@ -803,8 +941,10 @@ Runs multiple L</termvector()> requests in a single request, eg:
);
Query string parameters:
+ C<error_trace>,
C<field_statistics>,
C<fields>,
+ C<human>,
C<ids>,
C<offsets>,
C<parent>,
@@ -864,30 +1004,38 @@ L<request body|http://www.elastic.co/guide/en/elasticsearch/reference/current/se
Query string parameters:
C<_source>,
- C<_source_exclude>,
- C<_source_include>,
+ C<_source_excludes>,
+ C<_source_includes>,
C<allow_no_indices>,
+ C<allow_partial_search_results>,
C<analyze_wildcard>,
C<analyzer>,
+ C<batched_reduce_size>,
C<default_operator>,
C<df>,
+ C<docvalue_fields>,
+ C<error_trace>,
C<expand_wildcards>,
C<explain>,
- C<fielddata_fields>,
- C<fields>,
C<from>,
+ C<human>,
+ C<ignore_throttled>,
C<ignore_unavailable>,
C<lenient>,
- C<lowercase_expanded_terms>,
+ C<max_concurrent_shard_requests>,
+ C<pre_filter_shard_size>,
C<preference>,
C<q>,
C<request_cache>,
+ C<rest_total_hits_as_int>,
C<routing>,
C<scroll>,
C<search_type>,
+ C<seq_no_primary_term>,
C<size>,
C<sort>,
C<stats>,
+ C<stored_fields>,
C<suggest_field>,
C<suggest_mode>,
C<suggest_size>,
@@ -895,6 +1043,8 @@ Query string parameters:
C<terminate_after>,
C<timeout>,
C<track_scores>,
+ C<track_total_hits>,
+ C<typed_keys>,
C<version>
See the L<search reference|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html>
@@ -902,37 +1052,6 @@ for more information.
Also see L<Search::Elasticsearch::Transport/send_get_body_as>.
-=head2 C<search_exists()>
-
-The C<search_exists()> method is a quick version of search which can be
-used to find out whether there are matching search results or not.
-It doesn't return any results itself.
-
- $results = $e->search_exists(
- index => 'index' | \@indices, # optional
- type => 'type' | \@types, # optional
-
- body => { search params } # optional
- );
-
-Query string parameters:
- C<allow_no_indices>,
- C<analyze_wildcard>,
- C<analyzer>,
- C<default_operator>,
- C<df>,
- C<expand_wildcards>,
- C<ignore_unavailable>,
- C<lenient>,
- C<lowercase_expanded_terms>
- C<min_score>,
- C<preference>,
- C<q>,
- C<routing>
-
-See the L<search exists reference|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-exists.html>
-for more information.
-
=head2 C<count()>
$results = $e->count(
@@ -959,14 +1078,18 @@ Query string parameters:
C<analyzer>,
C<default_operator>,
C<df>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
+ C<ignore_throttled>,
C<ignore_unavailable>,
C<lenient>,
C<lowercase_expanded_terms>
C<min_score>,
C<preference>,
C<q>,
- C<routing>
+ C<routing>,
+ C<terminate_after>
See the L<count docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html>
for more information.
@@ -977,7 +1100,7 @@ for more information.
index => 'index' | \@indices, # optional
type => 'type' | \@types, # optional
- body => { search params } # optional
+ body => { search params } # required
);
Perform a search by specifying a template (either predefined or defined
@@ -985,7 +1108,7 @@ within the C<body>) and parameters to use with the template, eg:
$results = $e->search_template(
body => {
- inline => {
+ source => {
query => {
match => {
"{{my_field}}" => "{{my_value}}"
@@ -996,7 +1119,7 @@ within the C<body>) and parameters to use with the template, eg:
params => {
my_field => 'foo',
my_value => 'bar',
- my_size => 5
+ my_size => 6
}
}
);
@@ -1006,11 +1129,18 @@ for more information.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<explain>,
+ C<human>,
+ C<ignore_throttled>,
C<ignore_unavailable>,
C<preference>,
+ C<profile>,
+ C<rest_total_hits_as_int>,
C<scroll>,
- C<search_type>
+ C<search_type>,
+ C<typed_keys>
=head2 C<render_search_template()>
@@ -1023,7 +1153,7 @@ Renders the template, filling in the passed-in parameters and returns the result
$results = $e->render_search_template(
body => {
- inline => {
+ source => {
query => {
match => {
"{{my_field}}" => "{{my_value}}"
@@ -1034,7 +1164,7 @@ Renders the template, filling in the passed-in parameters and returns the result
params => {
my_field => 'foo',
my_value => 'bar',
- my_size => 5
+ my_size => 6
}
}
);
@@ -1046,7 +1176,9 @@ for more information.
$results = $e->scroll(
scroll => '1m',
- scroll_id => $id
+ body => {
+ scroll_id => $id
+ }
);
When a L</search()> has been performed with the
@@ -1054,14 +1186,13 @@ C<scroll> parameter, the C<scroll()>
method allows you to keep pulling more results until the results
are exhausted.
-B<NOTE:> you will almost always want to set the
-C<search_type> to C<scan> in your
-original C<search()> request.
-
-See L</scroll_helper()> and L<Search::Elasticsearch::Client::2_0::Scroll> for a helper utility
+See L</scroll_helper()> and L<Search::Elasticsearch::Client::7_0::Scroll> for a helper utility
which makes managing scroll requests much easier.
Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<rest_total_hits_as_int>,
C<scroll>,
C<scroll_id>
@@ -1072,13 +1203,9 @@ for more information.
=head2 C<clear_scroll()>
$response = $e->clear_scroll(
- scroll_id => $id | \@ids # required
- );
-
-Or
-
- $response = $e->clear_scroll(
- body => $id
+ body => {
+ scroll_id => $id | \@ids # required
+ }
);
The C<clear_scroll()> method can clear unfinished scroll requests, freeing
@@ -1089,7 +1216,8 @@ up resources on the server.
$scroll_helper = $e->scroll_helper( @args );
Returns a new instance of the class specified in the L</scroll_helper_class>,
-which defaults to L<Search::Elasticsearch::Client::2_0::Scroll>.
+which defaults to L<Search::Elasticsearch::Client::7_0::Scroll>.
+
=head2 C<msearch()>
@@ -1121,11 +1249,58 @@ request). For instance:
);
Query string parameters:
- C<search_type>
+ C<error_trace>,
+ C<human>,
+ C<max_concurrent_searches>,
+ C<max__concurrent_shard_requests>,
+ C<pre_filter_shard_size>,
+ C<rest_total_hits_as_int>,
+ C<search_type>,
+ C<typed_keys>
See the L<msearch docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-multi-search.html>
for more information.
+=head2 C<msearch_template()>
+
+ $results = $e->msearch_template(
+ index => 'default_index' | \@indices, # optional
+ type => 'default_type' | \@types, # optional
+
+ body => [ search_templates ] # required
+ );
+
+The C<msearch_template()> method allows you to perform multiple searches in a single
+request using search templates. Similar to the L</bulk()> request, each search
+request in the C<body> consists of two hashes: the metadata hash then the search request
+hash (the same data that you'd specify in the C<body> of a L</search()>
+request). For instance:
+
+ $results = $e->msearch(
+ index => 'default_index',
+ type => ['default_type_1', 'default_type_2'],
+ body => [
+ # uses defaults
+ {},
+ { source => { query => { match => { user => "{{user}}" }}} params => { user => 'joe' }},
+
+ # uses a custom index
+ { index => 'not_the_default_index' },
+ { source => { query => { match => { user => "{{user}}" }}} params => { user => 'joe' }},
+ ]
+ );
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<max_concurrent_searches>,
+ C<rest_total_hits_as_int>,
+ C<search_type>,
+ C<typed_keys>
+
+See the L<msearch-template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-multi-search.html>
+for more information.
+
=head2 C<explain()>
$response = $e->explain(
@@ -1153,50 +1328,48 @@ For instance:
Query string parameters:
C<_source>,
- C<_source_exclude>,
- C<_source_include>,
+ C<_source_excludes>,
+ C<_source_includes>,
C<analyze_wildcard>,
C<analyzer>,
C<default_operator>,
C<df>,
- C<fields>,
+ C<error_trace>,
+ C<human>,
C<lenient>,
- C<lowercase_expanded_terms>,
C<parent>,
C<preference>,
C<q>,
- C<routing>
+ C<routing>,
+ C<stored_fields>
See the L<explain docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html>
for more information.
-=head2 C<field_stats()>
+=head2 C<field_caps()>
- $response = $e->field_stats(
+ $response = $e->field_caps(
index => 'index' | \@indices, # optional
- fields => 'field' | \@fields, # optional
- level => 'cluster' | 'indices', # optional
body => { filters } # optional
);
-The C<field-stats> API returns statistical properties of a field
-(such as min and max values) without executing a search.
+The C<field-caps> API returns field types and abilities, merged across indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<fields>,
- C<ignore_unavailable>,
- C<level>
+ C<human>,
+ C<ignore_unavailable>
-See the L<field-stats docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-field-stats.html>
+See the L<field-caps docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-field-caps.html>
for more information.
=head2 C<search_shards()>
$response = $e->search_shards(
index => 'index' | \@indices, # optional
- type => 'type' | \@types, # optional
)
The C<search_shards()> method returns information about which shards on
@@ -1204,7 +1377,9 @@ which nodes will execute a search request.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
C<local>,
C<preference>,
@@ -1213,84 +1388,55 @@ Query string parameters:
See the L<search-shards docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-shards.html>
for more information.
-=head1 CRUD-BY-QUERY METHODS
-
-=head2 C<delete_by_query()>
+=head2 C<rank_eval()>
- $response = $e->delete_by_query(
+ $result = $e->rank_eval(
index => 'index' | \@indices, # optional
- type => 'type' | \@types, # optional,
- body => { delete-by-query } # optional
+ body => {...} # required
);
-The C<delete_by_query()> method (available with the
-L<delete-by-query plugin|https://www.elastic.co/guide/en/elasticsearch/plugins/current/plugins-delete-by-query.html>)
-deletes all documents which match the specified query.
+The ranking evaluation API provides a way to execute test cases to determine whether search results
+are improving or worsening.
Query string parameters:
C<allow_no_indices>,
- C<analyzer>,
- C<default_operator>,
- C<df>,
+ C<error_trace>,
C<expand_wildcards>,
C<filter_path>,
- C<ignore_unavailable>,
- C<q>,
- C<routing>,
- C<timeout>
+ C<human>,
+ C<ignore_unavailable>
-See the L<delete-by-query docs|https://www.elastic.co/guide/en/elasticsearch/plugins/current/plugins-delete-by-query.html>
+See the L<rank-eval docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html>
for more information.
-=head2 C<reindex()>
-
- $response = $e->reindex(
- body => { reindex } # required
- );
-
-The C<reindex()> API is used to index documents from one index or multiple indices
-to a new index.
-
-Query string parameters:
- C<consistency>,
- C<refresh>,
- C<requests_per_second>,
- C<timeout>,
- C<wait_for_completion>
-
-See the L<reindex docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html>
-for more information.
+=head1 CRUD-BY-QUERY METHODS
-=head2 C<update_by_query()>
+=head2 C<delete_by_query()>
- $response = $e->update_by_query(
+ $response = $e->delete_by_query(
index => 'index' | \@indices, # optional
type => 'type' | \@types, # optional,
- body => { update-by-query } # optional
+ body => { delete-by-query } # required
);
-The C<update_by_query()> API is used to bulk update documents from one index or
-multiple indices using a script.
+The C<delete_by_query()> method deletes all documents which match the specified query.
Query string parameters:
C<_source>,
- C<_source_exclude>,
- C<_source_include>,
+ C<_source_excludes>,
+ C<_source_includes>,
C<allow_no_indices>,
C<analyze_wildcard>,
C<analyzer>,
C<conflicts>,
- C<consistency>,
C<default_operator>,
C<df>,
+ C<error_trace>,
C<expand_wildcards>,
- C<explain>,
- C<fielddata_fields>,
- C<fields>,
C<from>,
+ C<human>,
C<ignore_unavailable>,
C<lenient>,
- C<lowercase_expanded_terms>,
C<preference>,
C<q>,
C<refresh>,
@@ -1302,328 +1448,226 @@ Query string parameters:
C<search_timeout>,
C<search_type>,
C<size>,
+ C<slices>,
C<sort>,
C<stats>,
- C<suggest_field>,
- C<suggest_mode>,
- C<suggest_size>,
- C<suggest_text>,
C<terminate_after>,
- C<timeout>,
- C<track_scores>,
C<version>,
- C<version_type>,
+ C<timeout>,
+ C<wait_for_active_shards>,
C<wait_for_completion>
-See the L<update_by_query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html>
+See the L<delete-by-query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html>
for more information.
-=head2 C<reindex_rethrottle>
+=head2 C<delete_by_query_rethrottle()>
- $response = $e->reindex_rethrottle(
- task_id => 'task_id', # required
- requests_per_second => $req_per_second
+ $response = $e->delete_by_query_rethrottle(
+ task_id => 'id' # required
+ requests_per_second => num
);
-The C<reindex_rethrottle()> API is used to dynamically update the throtting
-of an existing reindex request, identified by C<task_id>.
+The C<delete_by_query_rethrottle()> API is used to dynamically update the throtting
+of an existing delete-by-query request, identified by C<task_id>.
Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
C<requests_per_second>
-See the L<reindex docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html>
+See the L<delete-by-query-rethrottle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html>
for more information.
-=head1 PERCOLATION METHODS
+=head2 C<reindex()>
-=head2 C<percolate()>
+ $response = $e->reindex(
+ body => { reindex } # required
+ );
- $results = $e->percolate(
- index => 'my_index', # required
- type => 'my_type', # required
+The C<reindex()> API is used to index documents from one index or multiple indices
+to a new index.
- body => { percolation } # required
- );
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<refresh>,
+ C<requests_per_second>,
+ C<slices>,
+ C<timeout>,
+ C<wait_for_active_shards>,
+ C<wait_for_completion>
-Percolation is search inverted: instead of finding docs which match a
-particular query, it finds queries which match a particular document, eg
-for I<alert-me-when> functionality.
+See the L<reindex docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html>
+for more information.
-The C<percolate()> method runs a percolation request to find the
-queries matching a particular document. In the C<body> you should pass the
-C<_source> field of the document under the C<doc> key:
+=head2 C<reindex_rethrottle()>
- $results = $e->percolate(
- index => 'my_index',
- type => 'my_type',
- body => {
- doc => {
- title => 'Elasticsearch rocks'
- }
- }
+ $response = $e->delete_by_query_rethrottle(
+ task_id => 'id', # required
+ requests_per_second => num
);
+The C<reindex_rethrottle()> API is used to dynamically update the throtting
+of an existing reindex request, identified by C<task_id>.
+
Query string parameters:
- C<allow_no_indices>,
- C<expand_wildcards>,
- C<ignore_unavailable>,
- C<percolate_format>,
- C<percolate_index>,
- C<percolate_preference>,
- C<percolate_routing>,
- C<percolate_type>,
- C<preference>,
- C<routing>,
- C<version>,
- C<version_type>
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<requests_per_second>
-See the L<percolate docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-percolate.html>
+See the L<reindex-rethrottle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html>
for more information.
-=head2 C<count_percolate()>
- $results = $e->count_percolate(
- index => 'my_index', # required
- type => 'my_type', # required
+=head2 C<update_by_query()>
- body => { percolation } # required
+ $response = $e->update_by_query(
+ index => 'index' | \@indices, # optional
+ type => 'type' | \@types, # optional,
+ body => { update-by-query } # optional
);
-The L</count_percolate()> request works just like the L</percolate()>
-request except that it returns a count of all matching queries, instead
-of the queries themselves.
-
- $results = $e->count_percolate(
- index => 'my_index',
- type => 'my_type',
- body => {
- doc => {
- title => 'Elasticsearch rocks'
- }
- }
- );
+The C<update_by_query()> API is used to bulk update documents from one index or
+multiple indices using a script.
Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
C<allow_no_indices>,
+ C<analyze_wildcard>,
+ C<analyzer>,
+ C<conflicts>,
+ C<default_operator>,
+ C<df>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<from>,
+ C<human>,
C<ignore_unavailable>,
- C<percolate_index>,
- C<percolate_type>,
+ C<lenient>,
+ C<pipeline>,
C<preference>,
+ C<q>,
+ C<refresh>,
+ C<request_cache>,
+ C<requests_per_second>,
C<routing>,
+ C<scroll>,
+ C<scroll_size>,
+ C<search_timeout>,
+ C<search_type>,
+ C<size>,
+ C<slices>,
+ C<sort>,
+ C<stats>,
+ C<terminate_after>,
+ C<timeout>,
C<version>,
- C<version_type>
+ C<version_type>,
+ C<wait_for_active_shards>,
+ C<wait_for_completion>
-See the L<percolate docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-percolate.html>
+See the L<update_by_query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html>
for more information.
-=head2 C<mpercolate()>
-
- $results = $e->mpercolate(
- index => 'my_index', # required if type
- type => 'my_type', # optional
+=head2 C<update_by_query_rethrottle()>
- body => [ percolation requests ] # required
+ $response = $e->update_by_query_rethrottle(
+ task_id => 'id' # required
+ requests_per_second => num
);
-Multi-percolation allows multiple L</percolate()> requests to be run
-in a single request.
-
- $results = $e->mpercolate(
- index => 'my_index',
- type => 'my_type',
- body => [
- # first request
- { percolate => {
- index => 'twitter',
- type => 'tweet'
- }},
- { doc => {message => 'some_text' }},
-
- # second request
- { percolate => {
- index => 'twitter',
- type => 'tweet',
- id => 1
- }},
- {},
- ]
- );
+The C<update_by_query_rethrottle()> API is used to dynamically update the throtting
+of an existing update-by-query request, identified by C<task_id>.
Query string parameters:
- C<allow_no_indices>,
- C<expand_wildcards>,
- C<ignore_unavailable>
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<requests_per_second>
-See the L<mpercolate docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-percolate.html>
+See the L<update-by-query-rethrottle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html>
for more information.
-=head2 C<suggest()>
-
- $results = $e->suggest(
- index => 'index' | \@indices, # optional
-
- body => { suggest request } # required
- );
-
-The C<suggest()> method is used to run
-L<did-you-mean|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesteres-phrase.html>
-or L<search-as-you-type|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html>
-suggestion requests, which can also be run as part of a L</search()> request.
-
- $results = $e->suggest(
- index => 'my_index',
- body => {
- my_suggestions => {
- phrase => {
- text => 'johnny walker',
- field => 'title'
- }
- }
- }
- );
-
-Query string parameters:
- C<allow_no_indices>,
- C<expand_wildcards>,
- C<ignore_unavailable>,
- C<preference>,
- C<routing>
=head1 INDEXED SCRIPT METHODS
-If dynamic scripting is enabled, Elasticsearch allows you to store scripts in an internal index known as
-C<.scripts> and reference them by id. The methods to manage indexed scripts are as follows:
+Elasticsearch allows you to store scripts in the cluster state
+and reference them by id. The methods to manage indexed scripts are as follows:
=head2 C<put_script()>
$result = $e->put_script(
- lang => 'lang', # required
- id => 'id', # required
- body => { script } # required
+ id => 'id', # required
+ context => $context, # optional
+ body => { script } # required
);
-The C<put_script()> method is used to store a script in the C<.scripts> index. For instance:
+The C<put_script()> method is used to store a script in the cluster state. For instance:
$result = $e->put_scripts(
- lang => 'groovy',
id => 'hello_world',
body => {
- script => q(return "hello world");
+ script => {
+ lang => 'painless',
+ source => q(return "hello world")
+ }
}
);
Query string parameters:
- C<op_type>,
- C<version>,
- C<version_type>
+ C<error_trace>,
+ C<human>
+
See the L<indexed scripts docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_indexed_scripts> for more.
=head2 C<get_script()>
$script = $e->get_script(
- lang => 'lang', # required
id => 'id', # required
);
-Retrieve the indexed script from the C<.scripts> index.
+Retrieve the indexed script from the cluster state.
Query string parameters:
- C<version>,
- C<version_type>
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
See the L<indexed scripts docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_indexed_scripts> for more.
=head2 C<delete_script()>
$script = $e->delete_script(
- lang => 'lang', # required
id => 'id', # required
);
-Delete the indexed script from the C<.scripts> index.
+Delete the indexed script from the cluster state.
Query string parameters:
- C<version>,
- C<version_type>
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
See the L<indexed scripts docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_indexed_scripts> for more.
-=head1 INDEXED SEARCH TEMPLATE METHODS
-
-Mustache templates can be used to create search requests. These templates can
-be stored in the C<.scripts> index and retrieved by ID. The methods to
-manage indexed scripts are as follows:
+=head2 C<scripts_painless_execute()>
-=head2 C<put_template()>
-
- $result = $e->put_template(
- id => 'id', # required
- body => { template } || "template" # required
+ $result = $e->scripts_painless_execute(
+ body => {...} # required
);
-The C<put_template()> method is used to store a template in the C<.scripts> index.
-For instance:
-
- $result = $e->put_template(
- id => 'hello_world',
- body => {
- template => {
- query => {
- match => {
- title => "hello world"
- }
- }
- }
- }
- );
-
-Query string parameters: None
-
-See the L<indexed search template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html#_pre_registered_template> for more.
-
-=head2 C<get_template()>
-
- $script = $e->get_template(
- id => 'id', # required
- );
-
-Retrieve the indexed template from the C<.scripts> index.
+The Painless execute API allows an arbitrary script to be executed and a result to be returned.
Query string parameters:
- C<version>,
- C<version_type>
-
-See the L<indexed search template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html#_pre_registered_template> for more.
-
-=head2 C<delete_template()>
-
- $script = $e->delete_template(
- id => 'id', # required
- );
-
-Delete the indexed template from the C<.scripts> index.
-
-Query string parameters: None
-
-See the L<indexed search template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html#_pre_registered_template> for more.
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
-# ABSTRACT: Thin client with full support for Elasticsearch 2.x APIs
+See the L<painless execution docs|https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-execute-api.html> for more.
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/AsyncSearch.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/AsyncSearch.pm
new file mode 100644
index 0000000..1a03293
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/AsyncSearch.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::AsyncSearch;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('async_search');
+
+1;
+
+__END__
+
+# ABSTRACT: Async Search feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Async Search is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/async-search.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ $response = $es->async_search->get(
+ id => $id # required
+ )
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Autoscaling.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Autoscaling.pm
new file mode 100644
index 0000000..1310fba
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Autoscaling.pm
@@ -0,0 +1,43 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Autoscaling;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('autoscaling');
+
+1;
+
+__END__
+
+# ABSTRACT: Autoscaling feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Autoscaling is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/7.x/autoscaling-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ $response = $es->autoscaling->get();
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/CCR.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/CCR.pm
new file mode 100644
index 0000000..bef6c7f
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/CCR.pm
@@ -0,0 +1,228 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::CCR;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('ccr');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing cross-cluster replication APIs for Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+This module provides methods to use the cross-cluster replication feature.
+
+The full documentation for CCR is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ $response = $es->ccr->follow(
+ index => $index, # required
+ body => {...} # required
+ )
+
+The C<follow()> method creates a new follower index that is configured to follow the referenced leader index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<wait_for_active_shards>
+
+See the L<CCR follow docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-put-follow.html>
+for more information.
+
+
+=head2 C<pause_follow()>
+
+ $response = $es->ccr->pause_follow(
+ index => $index, # required
+ )
+
+The C<pause_follow()> method pauses following of an index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR pause follow docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-pause-follow.html>
+for more information.
+
+
+=head2 C<resume_follow()>
+
+ $response = $es->ccr->resume_follow(
+ index => $index, # required
+ )
+
+The C<resume_follow()> method resumes following of an index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR resume follow docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-resume-follow.html>
+for more information.
+
+
+=head2 C<unfollow()>
+
+ $response = $es->ccr->unfollow(
+ index => $index, # required
+ )
+
+The C<unfollow()> method converts a follower index into a normal index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR unfollow docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-unfollow.html>
+for more information.
+
+
+=head2 C<forget_follower()>
+
+ $response = $es->ccr->forget_follower(
+ index => $index, # required
+ )
+
+The C<forget_follower()> method removes the follower retention leases from the leader.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR forget_follower docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-forget-follower.html>
+for more information.
+
+=head1 STATS METHODS
+
+=head2 C<stats()>
+
+ $response = $es->ccr->stats()
+
+The C<stats()> method returns all stats related to cross-cluster replication.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR stats docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-stats.html>
+for more information.
+
+=head2 C<follow_stats()>
+
+ $response = $es->ccr->follow_stats(
+ index => $index | \@indices, # optional
+ )
+
+The C<follow_stats()> method returns shard-level stats about follower indices.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR follow stats docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-follow-stats.html>
+for more information.
+
+
+=head2 C<follow_info()>
+
+ $response = $es->ccr->follow_info(
+ index => $index | \@indices, # optional
+ )
+
+The C<follow_info()> method returns the parameters and the status for each follower index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR follow info docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-follow-info.html>
+for more information.
+
+=head1 AUTO-FOLLOW METHODS
+
+=head2 C<put_auto_follow_pattern()>
+
+ $response = $es->ccr->put_auto_follow_pattern(
+ name => $name # required
+ )
+
+The C<put_auto_follow_pattern()> method creates a new named collection of auto-follow patterns against the remote cluster specified in the request body.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR put auto follow pattern docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-put-auto-follow-pattern.html>
+for more information.
+
+
+=head2 C<get_auto_follow_pattern()>
+
+ $response = $es->ccr->get_auto_follow_pattern(
+ name => $name # optional
+ )
+
+The C<get_auto_follow_pattern()> method retrieves a named collection of auto-follow patterns, or all patterns.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR get auto follow pattern docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-auto-follow-pattern.html>
+for more information.
+
+=head2 C<delete_auto_follow_pattern()>
+
+ $response = $es->ccr->delete_auto_follow_pattern(
+ name => $name # required
+ )
+
+The C<delete_auto_follow_pattern()> method deletes a named collection of auto-follow patterns.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR delete auto follow pattern docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-delete-auto-follow-pattern.html>
+for more information.
+
+
diff --git a/lib/Search/Elasticsearch/Client/2_0/Direct/Cat.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Cat.pm
similarity index 76%
rename from lib/Search/Elasticsearch/Client/2_0/Direct/Cat.pm
rename to lib/Search/Elasticsearch/Client/7_0/Direct/Cat.pm
index a1b3cd0..5f85b52 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Direct/Cat.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Cat.pm
@@ -1,7 +1,11 @@
-package Search::Elasticsearch::Client::2_0::Direct::Cat;
-$Search::Elasticsearch::Client::2_0::Direct::Cat::VERSION = '6.81';
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Client::7_0::Direct::Cat;
+
use Moo;
-with 'Search::Elasticsearch::Client::2_0::Role::API';
+with 'Search::Elasticsearch::Client::7_0::Role::API';
with 'Search::Elasticsearch::Role::Client::Direct';
use Search::Elasticsearch::Util qw(parse_params);
use namespace::clean;
@@ -16,19 +20,25 @@ sub help {
$self->perform_request( $defn, $params );
}
-1;
-
-=pod
+#===================================
+around 'perform_request' => sub {
+#===================================
+ my $orig = shift;
+ my $self = shift;
+ my ( $defn, $params ) = parse_params(@_);
+ if ( $params->{help} && $params->{help} ne 'false' ) {
+ $defn = { %$defn, parts => {} };
+ }
-=encoding UTF-8
+ return $orig->( $self, $defn, $params );
-=head1 NAME
+};
-Search::Elasticsearch::Client::2_0::Direct::Cat - A client for running cat debugging requests
+1;
-=head1 VERSION
+__END__
-version 6.81
+# ABSTRACT: A client for running cat debugging requests
=head1 DESCRIPTION
@@ -63,6 +73,7 @@ Accepts a list of column names to be output, eg:
Formats byte-based values as bytes (C<b>), kilobytes (C<k>), megabytes
(C<m>) or gigabytes (C<g>)
+
=back
It does L<Search::Elasticsearch::Role::Client::Direct>.
@@ -85,10 +96,14 @@ Returns information about index aliases, optionally limited to the specified
index/alias names.
Query string parameters:
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat aliases docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-aliases.html>
@@ -105,10 +120,14 @@ state of disk usage.
Query string parameters:
C<bytes>,
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat allocation docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-allocation.html>
@@ -124,10 +143,14 @@ Provides quick access to the document count of the entire cluster, or
individual indices.
Query string parameters:
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat count docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-count.html>
@@ -144,10 +167,14 @@ fields) loaded into fielddata.
Query string parameters:
C<bytes>,
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat fielddata docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-fielddata.html>
@@ -161,12 +188,15 @@ Provides a snapshot of how shards have located around the cluster and the
state of disk usage.
Query string parameters:
- C<bytes>,
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
C<ts>,
+ C<s>,
C<v>
See the L<cat health docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-health.html>
@@ -183,11 +213,16 @@ or individual indices
Query string parameters:
C<bytes>,
+ C<error_trace>,
+ C<format>,
C<h>,
+ C<health>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
C<pri>,
+ C<s>,
C<v>
See the L<cat indices docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-indices.html>
@@ -200,10 +235,14 @@ for more information.
Displays the master’s node ID, bound IP address, and node name.
Query string parameters:
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat master docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-master.html>
@@ -216,10 +255,14 @@ for more information.
Returns the node attributes set per node.
Query string parameters:
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat nodeattrs docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodeattrs.html>
@@ -232,10 +275,14 @@ for more information.
Provides a snapshot of all of the nodes in your cluster.
Query string parameters:
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat nodes docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodes.html>
@@ -248,10 +295,14 @@ for more information.
Returns any cluster-level tasks which are queued on the master.
Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<human>,
C<local>,
C<master_timeout>,
C<h>,
C<help>,
+ C<s>,
C<v>
See the L<cat pending-tasks docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-pending-tasks.html>
@@ -264,10 +315,14 @@ for more information.
Returns information about plugins installed on each node.
Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<human>,
C<local>,
C<master_timeout>,
C<h>,
C<help>,
+ C<s>,
C<v>
See the L<cat plugins docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-plugins.html>
@@ -286,9 +341,13 @@ stuck, try it to see if there’s any movement using C<recovery()>.
Query string parameters:
C<bytes>,
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat recovery docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-recovery.html>
@@ -301,10 +360,14 @@ for more information.
Provides a list of registered snapshot repositories.
Query string parameters:
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat repositories docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-repositories.html>
@@ -319,8 +382,13 @@ for more information.
Provides low level information about the segments in the shards of an index.
Query string parameters:
+ C<bytes>,
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
+ C<s>,
C<v>
See the L<cat shards docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-segments.html>
@@ -336,10 +404,15 @@ Provides a detailed view of what nodes contain which shards, the state and
size of each shard.
Query string parameters:
+ C<bytes>,
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat shards docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-shards.html>
@@ -348,21 +421,71 @@ for more information.
=head2 C<snapshots()>
say $e->cat->snapshots(
- repository => 'repository' | \@repositories # required
+ repository => 'repository' | \@repositories # optional
)
Provides a list of all snapshots that belong to the specified repositories.
Query string parameters:
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<ignore_unavailable>,
C<master_timeout>,
+ C<s>,
C<v>
See the L<cat snapshots docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-snapshots.html>
for more information.
+=head2 C<tasks()>
+
+ say $e->cat->tasks()
+
+Provides a list of node-level tasks.
+
+Query string parameters:
+ C<actions>,
+ C<detailed>,
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<node_id>,
+ C<parent_node>,
+ C<parent_task>,
+ C<s>,
+ C<v>
+
+See the L<cat tasks docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
+for more information.
+
+=head2 C<templates()>
+
+ say $e->cat->templates(
+ name => $name # optional
+ )
+
+Provides a list of index templates.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat templates docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/templates.html>
+for more information.
+
+
=head2 C<thread_pool()>
say $e->cat->thread_pool(
@@ -374,31 +497,17 @@ C<queue> and C<rejected> statistics are returned for the C<bulk>, C<index> and
C<search> thread pools.
Query string parameters:
- C<full_id>,
+ C<error_trace>,
+ C<format>,
C<h>,
C<help>,
+ C<human>,
C<local>,
C<master_timeout>,
+ C<size>,
+ C<s>,
C<v>
See the L<cat thread_pool docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-thread-pool.html>
for more information.
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
-
-# ABSTRACT: A client for running cat debugging requests
-
diff --git a/lib/Search/Elasticsearch/Client/2_0/Direct/Cluster.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Cluster.pm
similarity index 67%
rename from lib/Search/Elasticsearch/Client/2_0/Direct/Cluster.pm
rename to lib/Search/Elasticsearch/Client/7_0/Direct/Cluster.pm
index aff91c4..1e13e50 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Direct/Cluster.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Cluster.pm
@@ -1,23 +1,32 @@
-package Search::Elasticsearch::Client::2_0::Direct::Cluster;
-$Search::Elasticsearch::Client::2_0::Direct::Cluster::VERSION = '6.81';
+# 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 Search::Elasticsearch::Client::7_0::Direct::Cluster;
+
use Moo;
-with 'Search::Elasticsearch::Client::2_0::Role::API';
+with 'Search::Elasticsearch::Client::7_0::Role::API';
with 'Search::Elasticsearch::Role::Client::Direct';
__PACKAGE__->_install_api('cluster');
1;
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Direct::Cluster - A client for running cluster-level requests
-
-=head1 VERSION
+__END__
-version 6.81
+# ABSTRACT: A client for running cluster-level requests
=head1 DESCRIPTION
@@ -40,13 +49,17 @@ health, returning C<red>, C<yellow> or C<green> to indicate the state
of the cluster, indices or shards.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<level>,
C<local>,
C<master_timeout>,
C<timeout>,
C<wait_for_active_shards>,
+ C<wait_for_events>,
+ C<wait_for_no_initializing_shards>,
+ C<wait_for_no_relocating_shards>,
C<wait_for_nodes>,
- C<wait_for_relocating_shards>,
C<wait_for_status>
See the L<cluster health docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html>
@@ -61,6 +74,7 @@ for more information.
Returns high-level cluster stats, optionally limited to the listed nodes.
Query string parameters:
+ C<error_trace>,
C<flat_settings>,
C<human>,
C<timeout>
@@ -76,7 +90,10 @@ The C<get_settings()> method is used to retrieve cluster-wide settings that
have been set with the L</put_settings()> method.
Query string parameters:
+ C<error_trace>,
C<flat_settings>,
+ C<human>,
+ C<include_defaults>,
C<master_timeout>,
C<timeout>
@@ -95,12 +112,14 @@ For instance:
$response = $e->cluster->put_settings(
body => {
- transient => { "discovery.zen.minimum_master_nodes" => 5 }
+ transient => { "discovery.zen.minimum_master_nodes" => 6 }
}
);
Query string parameters:
- C<flat_settings>
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>
See the L<cluster settings docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-update-settings.html>
for more information.
@@ -127,15 +146,36 @@ parameter.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<flat_settings>,
+ C<human>,
C<ignore_unavailable>,
C<local>,
- C<master_timeout>
+ C<master_timeout>,
+ C<wait_for_metadata_version>,
+ C<wait_for_timeout>
See the L<cluster state docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-state.html>
for more information.
+=head2 C<allocation_explain()>
+
+ $response = $e->cluster->allocation_explain(
+ body => { ... shard selectors ...} # optional
+ );
+
+Returns information about why a shard is allocated or unallocated or why.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<include_disk_info>,
+ C<include_yes_decisions>
+
+See the L<cluster allocation explain docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-allocation-explain.html>
+for more information.
+
=head2 C<pending_tasks()>
$response = $e->cluster->pending_tasks();
@@ -143,6 +183,8 @@ for more information.
Returns a list of cluster-level tasks still pending on the master node.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<local>,
C<master_timeout>
@@ -155,6 +197,7 @@ for more information.
body => { commands }
);
+
The C<reroute()> method is used to manually reallocate shards from one
node to another. The C<body> should contain the C<commands> indicating
which changes should be made. For instance:
@@ -179,29 +222,29 @@ which changes should be made. For instance:
Query string parameters:
C<dry_run>,
+ C<error_trace>,
C<explain>,
+ C<human>,
C<master_timeout>,
C<metric>,
+ C<retry_failed>,
C<timeout>
See the L<reroute docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-reroute.html>
for more information.
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
+=head2 C<remote_info()>
-=head1 COPYRIGHT AND LICENSE
+ $response = $e->cluster->remote_info();
-This software is Copyright (c) 2020 by Elasticsearch BV.
+The C<remote_info()> API retrieves all of the configured remote cluster information.
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
+Query string parameters:
+ C<error_trace>,
+ C<human>
-=cut
+See the L<remote_info docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-remote-info.html>
+for more information.
-__END__
-# ABSTRACT: A client for running cluster-level requests
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/DanglingIndices.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/DanglingIndices.pm
new file mode 100644
index 0000000..02dd59c
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/DanglingIndices.pm
@@ -0,0 +1,43 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::DanglingIndices;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('dangling_indices');
+
+1;
+
+__END__
+
+# ABSTRACT: Dangling indices feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Dangling Indices is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/master/indices.html#dangling-indices-api>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ $response = $es->dangling_indices->list_dangling_indices();
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/DataFrame.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/DataFrame.pm
new file mode 100644
index 0000000..fe5b5c5
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/DataFrame.pm
@@ -0,0 +1,55 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::DataFrame;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('data_frame');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Transform API for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->data_frame->explore(...);
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<data_frame>
+namespace, to support the API for the
+L<Transform|https://www.elastic.co/guide/en/elasticsearch/reference/7.3/transform-apis.html> plugin for Elasticsearch.
+
+=head1 METHODS
+
+The full documentation for the Transform plugin is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/7.3/transform-apis.html>
+
+=head2 C<explore()>
+
+ $response = $es->data_frame->put_data_frame_transform(
+ transform_id => $transform_id,
+ body => {...}
+ );
+
+The C<put_data_frame_transform()> instantiates a transform.
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/DataFrameTransformDeprecated.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/DataFrameTransformDeprecated.pm
new file mode 100644
index 0000000..7ee0517
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/DataFrameTransformDeprecated.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::DataFrameTransformDeprecated;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('data_frame_transform_deprecated');
+
+1;
+
+__END__
+
+# ABSTRACT: Data Frame Transform Deprecated feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Data Frame Transform Deprecated is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/7.x/transform-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->data_frame_transform_deprecated->get_transform_stats(
+ 'transform_id' => $transform_id
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Enrich.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Enrich.pm
new file mode 100644
index 0000000..161cd74
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Enrich.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Enrich;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('enrich');
+
+1;
+
+__END__
+
+# ABSTRACT: Enrich feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Enrich feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->enrich->get_policy(
+ 'name' => $name
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Eql.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Eql.pm
new file mode 100644
index 0000000..4686290
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Eql.pm
@@ -0,0 +1,46 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Eql;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('eql');
+
+1;
+
+__END__
+
+# ABSTRACT: Eql feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Eql feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->eql->search(
+ 'index' => $index,
+ 'body' => {...}
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Graph.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Graph.pm
new file mode 100644
index 0000000..1084f59
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Graph.pm
@@ -0,0 +1,66 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Graph;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('graph');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Graph API for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->graph->explore(...);
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<graph>
+namespace, to support the API for the
+L<Graph|https://www.elastic.co/guide/en/x-pack/current/xpack-graph.html> plugin for Elasticsearch.
+
+=head1 METHODS
+
+The full documentation for the Graph plugin is available here:
+L<https://www.elastic.co/guide/en/graph/current/index.html>
+
+=head2 C<explore()>
+
+ $response = $es->graph->explore(
+ index => $index | \@indices, # optional
+ type => $type | \@types, # optional
+ body => {...}
+ )
+
+The C<explore()> method allows you to discover vertices and connections which relate
+to your query.
+
+See the L<explore docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/graph-explore-api.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<routing>,
+ C<timeout>
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/ILM.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/ILM.pm
new file mode 100644
index 0000000..809e2eb
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/ILM.pm
@@ -0,0 +1,221 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::ILM;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('ilm');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing index lifecycle management APIs for Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+This module provides methods to use the index lifecycle management feature.
+
+The full documentation for ILM is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/index-lifecycle-management.html>
+
+=head1 POLICY METHODS
+
+=head2 C<put_lifecycle()>
+
+ $response = $es->ilm->put_lifecycle(
+ policy => $policy # required
+ body => {...} # required
+ )
+
+The C<put_lifecycle()> method creates or updates a lifecycle policy.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM put_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-put-lifecycle.html>
+for more information.
+
+=head2 C<put_lifecycle()>
+
+ $response = $es->ilm->put_lifecycle(
+ policy => $policy # required
+ body => {...} # required
+ )
+
+The C<put_lifecycle()> method creates or updates a lifecycle policy.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM put_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-put-lifecycle.html>
+for more information.
+
+
+=head2 C<get_lifecycle()>
+
+ $response = $es->ilm->get_lifecycle(
+ policy => $policy # required
+ )
+
+The C<get_lifecycle()> method retrieves the specified policy
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM get_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-get-lifecycle.html>
+for more information.
+
+=head2 C<delete_lifecycle()>
+
+ $response = $es->ilm->delete_lifecycle(
+ policy => $policy # required
+ )
+
+The C<delete_lifecycle()> method deletes the specified policy
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM delete_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-remove-lifecycle.html>
+for more information.
+
+=head1 INDEX MANAGEMENT METHODS
+
+=head2 C<move_to_step()>
+
+ $response = $es->ilm->move_to_step(
+ index => $index, # required
+ body => {...} # required
+ )
+
+The C<move_to_step()> method triggers execution of a specific step in the lifecycle policy.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM move_to_step docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-move-to-step.html>
+for more information.
+
+
+=head2 C<retry()>
+
+ $response = $es->ilm->retry(
+ index => $index, # required
+ )
+
+The C<retry()> method retries executing the policy for an index that is in the ERROR step.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM retry docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-retry.html>
+for more information.
+
+
+=head2 C<remove_lifecycle()>
+
+ $response = $es->ilm->remove_lifecycle(
+ index => $index # required
+ )
+
+The C<remove_lifecycle()> method removes a lifecycle from the specified index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM remove_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-remove-lifecycle.html>
+for more information.
+
+=head2 C<explain_lifecycle()>
+
+ $response = $es->ilm->explain_lifecycle(
+ index => $index # required
+ )
+
+The C<explain_lifecycle()> method returns information about the index’s current lifecycle state.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM explain_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-explain-lifecycle.html>
+for more information.
+
+
+=head1 OPERATION MANAGEMENT APIS
+
+=head2 C<status()>
+
+ $response = $es->ilm->status;
+
+The C<status()> method returns the current operating mode for ILM.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM status docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-get-status.html>
+for more information.
+
+=head2 C<start()>
+
+ $response = $es->ilm->start;
+
+The C<start()> method starts the index lifecycle management process.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM start docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-start.html>
+for more information.
+
+=head2 C<stop()>
+
+ $response = $es->ilm->stop;
+
+The C<stop()> method stops the index lifecycle management process.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM stop docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-stop.html>
+for more information.
diff --git a/lib/Search/Elasticsearch/Client/2_0/Direct/Indices.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Indices.pm
similarity index 79%
rename from lib/Search/Elasticsearch/Client/2_0/Direct/Indices.pm
rename to lib/Search/Elasticsearch/Client/7_0/Direct/Indices.pm
index 5776d89..78e56fc 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Direct/Indices.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Indices.pm
@@ -1,29 +1,38 @@
-package Search::Elasticsearch::Client::2_0::Direct::Indices;
-$Search::Elasticsearch::Client::2_0::Direct::Indices::VERSION = '6.81';
+# 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 Search::Elasticsearch::Client::7_0::Direct::Indices;
+
use Moo;
-with 'Search::Elasticsearch::Client::2_0::Role::API';
+with 'Search::Elasticsearch::Client::7_0::Role::API';
with 'Search::Elasticsearch::Role::Client::Direct';
__PACKAGE__->_install_api('indices');
1;
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Direct::Indices - A client for running index-level requests
-
-=head1 VERSION
+__END__
-version 6.81
+# ABSTRACT: A client for running index-level requests
=head1 DESCRIPTION
This module provides methods to make index-level requests, such as
creating and deleting indices, managing type mappings, index settings,
-warmers, index templates and aliases.
+index templates and aliases.
It does L<Search::Elasticsearch::Role::Client::Direct>.
@@ -37,17 +46,21 @@ It does L<Search::Elasticsearch::Role::Client::Direct>.
body => { # optional
index settings
mappings
- warmers
+ aliases
}
);
The C<create()> method is used to create an index. Optionally, index
-settings, type mappings and index warmers can be added at the same time.
+settings, type mappings, and aliases can be added at the same time.
Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<include_type_name>,
C<master_timeout>,
C<timeout>,
- C<update_all_types>
+ C<update_all_types>,
+ C<wait_for_active_shards>
See the L<create index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html>
for more information.
@@ -55,22 +68,24 @@ for more information.
=head2 C<get()>
$response = $e->indices->get(
- index => 'index' | \@indices # optional
- feature => 'feature' | \@features # optional
+ index => 'index' | \@indices # required
);
-Returns the aliases, settings, mappings, and warmers for the specified indices.
-The C<feature> parameter can be set to none or more of: C<_settings>, C<_mappings>,
-C<_warmers> and C<_aliases>.
+Returns the aliases, settings, and mappings for the specified indices.
See the L<get index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-index.html>.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<flat_settings>,
C<human>,
C<ignore_unavailable>,
- C<local>
+ C<include_defaults>,
+ C<include_type_name>,
+ C<local>,
+ C<master_timeout>
=head2 C<exists()>
@@ -83,8 +98,12 @@ whether the specified index or indices exist.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<flat_settings>,
+ C<human>,
C<ignore_unavailable>,
+ C<include_defaults>,
C<local>
See the L<index exists docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-indices-exists.html>
@@ -100,7 +119,9 @@ The C<delete()> method deletes the specified indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
C<master_timeout>,
C<timeout>
@@ -119,7 +140,9 @@ but allowing them to be reopened later.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>
C<master_timeout>,
C<timeout>
@@ -137,14 +160,129 @@ The C<open()> method opens closed indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>
C<master_timeout>,
- C<timeout>
+ C<timeout>,
+ C<wait_for_active_shards>
See the L<open index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html>
for more information.
+=head2 C<rollover()>
+
+ $response = $e->indices->rollover(
+ alias => $alias, # required
+ new_index => $index, # optional
+ body => { rollover conditions } # optional
+ );
+
+Rollover an index pointed to by C<alias> if it meets rollover conditions
+(eg max age, max docs) to a new index name.
+
+Query string parameters:
+ C<dry_run>,
+ C<error_trace>,
+ C<human>,
+ C<include_type_name>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<rollover index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-rollover-index.html>
+for more information.
+
+=head2 C<shrink()>
+
+ $response = $e->shrink(
+ index => $index, # required
+ target => $target, # required
+ body => { mappings, settings aliases } # optional
+ );
+
+The shrink API shrinks the shards of an index down to a single shard (or to a factor
+of the original shards).
+
+Query string parameters:
+ C<copy_settings>,
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<shrink index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-shrink-index.html>
+for more information.
+
+=head2 C<split()>
+
+ $response = $e->split(
+ index => $index, # required
+ target => $target, # required
+ );
+
+The split API splits a shard into multiple shards.
+
+Query string parameters:
+ C<copy_settings>,
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<split index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-split-index.html>
+for more information.
+
+=head2 C<freeze()>
+
+ $response = $e->indices->freeze(
+ $index => $index # required
+ );
+
+The C<freeze()> API is used to freeze an index, which puts it in a state which has almost no
+overhead on the cluster.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<filter_path>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<freeze index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/freeze-index-api.html>
+for more information.
+
+=head2 C<unfreeze()>
+
+ $response = $e->indices->unfreeze(
+ $index => $index # required
+ );
+
+The C<unfreeze()> API is used to return a frozen index to its normal state.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<filter_path>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<unfreeze index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/freeze-index-api.html>
+for more information.
+
=head2 C<clear_cache()>
$response = $e->indices->clear_cache(
@@ -156,12 +294,13 @@ or id cache for the specified indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<fielddata>,
C<fields>,
+ C<human>,
C<ignore_unavailable>,
C<query>,
- C<recycler>,
C<request>
See the L<clear_cache docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clearcache.html>
@@ -179,8 +318,9 @@ happens automatically once every second by default.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
- C<force>,
+ C<human>,
C<ignore_unavailable>
See the L<refresh index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html>
@@ -198,8 +338,10 @@ This process normally happens automatically.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<force>,
+ C<human>,
C<ignore_unavailable>,
C<wait_if_ongoing>
@@ -228,7 +370,9 @@ for more information.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>
=head2 C<forcemerge()>
@@ -243,40 +387,17 @@ with care, and only on indices that are no longer being updated.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<flush>,
+ C<human>,
C<ignore_unavailable>,
C<max_num_segments>,
- C<only_expunge_deletes>,
- C<wait_for_merge>
+ C<only_expunge_deletes>
See the L<forcemerge docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html>
for more information.
-=head2 C<optimize()>
-
-The C<optimize()> method is deprecated in 2.x and will be replaced by L<forcemerge()>;
-
- $response = $e->indices->optimize(
- index => 'index' | \@indices # optional
- );
-
-The C<optimize()> method rewrites all the data in an index into at most
-C<max_num_segments>. This is a very heavy operation and should only be run
-with care, and only on indices that are no longer being updated.
-
-Query string parameters:
- C<allow_no_indices>,
- C<expand_wildcards>,
- C<flush>,
- C<ignore_unavailable>,
- C<max_num_segments>,
- C<only_expunge_deletes>,
- C<wait_for_merge>
-
-See the L<optimize index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-optimize.html>
-for more information.
-
=head2 C<get_upgrade()>
$response = $e->indices->get_upgrade(
@@ -288,6 +409,7 @@ upgraded, which can be done with the C<upgrade()> method.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<human>,
C<ignore_unavailable>
@@ -305,7 +427,9 @@ The C<upgrade()> method upgrades all segments in the specified indices to the la
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
C<only_ancient_segments>,
C<wait_for_completion>
@@ -319,7 +443,7 @@ for more information.
$response = $e->indices->put_mapping(
index => 'index' | \@indices # optional,
- type => 'type', # required
+ type => 'type', # optional
body => { mapping } # required
)
@@ -345,8 +469,11 @@ For instance:
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
+ C<include_type_name>,
C<master_timeout>,
C<timeout>,
C<update_all_types>
@@ -354,6 +481,7 @@ Query string parameters:
See the L<put_mapping docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html>
for more information.
+
=head2 C<get_mapping()>
$result = $e->indices->get_mapping(
@@ -366,9 +494,13 @@ all types in one, more or all indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
- C<local>
+ C<include_type_name>,
+ C<local>,
+ C<master_timeout>
See the L<get_mapping docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-mapping.html>
for more information.
@@ -388,9 +520,12 @@ all fields in one, more or all types and indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
C<include_defaults>,
+ C<include_type_name>,
C<local>
See the L<get_mapping docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html>
@@ -408,7 +543,9 @@ in all specified indices, and returns C<1> or the empty string.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
C<local>
@@ -436,29 +573,14 @@ index aliases atomically. For instance:
);
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<timeout>
See the L<update_aliases docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
for more information.
-=head2 C<get_aliases()>
-
- $result = $e->indices->get_aliases(
- index => 'index' | \@indices, # optional
- alias => 'alias' | \@aliases # optional
- );
-
-The C<get_aliases()> method returns a list of aliases per index for all
-the specified indices.
-
-Query string parameters:
- C<local>,
- C<timeout>
-
-See the L<get_aliases docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
-for more information.
-
=head2 C<put_alias()>
$response = $e->indices->put_alias(
@@ -479,6 +601,8 @@ The C<put_alias()> method creates an index alias. For instance:
);
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<timeout>
@@ -497,7 +621,9 @@ aliases in the specified indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
C<local>
@@ -508,7 +634,7 @@ for more information.
$bool = $e->indices->exists_alias(
index => 'index' | \@indices, # optional
- name => 'alias' | \@aliases # optional
+ name => 'alias' | \@aliases # required
);
The C<exists_alias()> method returns C<1> or the empty string depending on
@@ -516,7 +642,9 @@ whether the specified aliases exist in the specified indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
+ C<human>,
C<ignore_unavailable>,
C<local>
@@ -534,6 +662,8 @@ The C<delete_alias()> method deletes one or more aliases from one or more
indices.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<timeout>
@@ -561,10 +691,14 @@ indices or all indices. For instance:
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<flat_settings>,
+ C<human>,
C<ignore_unavailable>,
- C<master_timeout>
+ C<master_timeout>,
+ C<preserve_existing>,
+ C<timeout>
See the L<put_settings docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html>
for more information.
@@ -581,11 +715,14 @@ indices or all indices.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<flat_settings>,
C<human>,
C<ignore_unavailable>,
- C<local>
+ C<include_defaults>,
+ C<local>,
+ C<master_timeout>
See the L<get_settings docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-settings.html>
for more information.
@@ -603,13 +740,13 @@ The C<put_template()> method is used to create or update index templates.
Query string parameters:
C<create>,
+ C<error_trace>,
C<flat_settings>,
+ C<human>,
+ C<include_type_name>,
C<master_timeout>,
- C<op_type>,
C<order>,
- C<timeout>,
- C<version>,
- C<version_type>
+ C<timeout>
See the L<put_template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html>
for more information.
@@ -623,7 +760,10 @@ for more information.
The C<get_template()> method is used to retrieve a named template.
Query string parameters:
+ C<error_trace>,
C<flat_settings>,
+ C<human>,
+ C<include_type_name>,
C<local>,
C<master_timeout>
@@ -633,12 +773,15 @@ for more information.
=head2 C<exists_template()>
$result = $e->indices->exists_template(
- name => 'template' # required
+ name => 'template' # optional
);
The C<exists_template()> method is used to check whether the named template exists.
Query string parameters:
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>,
C<local>,
C<master_timeout>
@@ -654,6 +797,8 @@ for more information.
The C<delete_template()> method is used to delete a named template.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<timeout>,
C<version>,
@@ -662,73 +807,6 @@ Query string parameters:
See the L<delete_template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html>
for more information.
-=head1 WARMER METHODS
-
-=head2 C<put_warmer()>
-
- $response = $e->indices->put_warmer(
- index => 'index' | \@indices, # optional
- type => 'type' | \@types, # optional
- name => 'warmer', # required
-
- body => { warmer defn } # required
- );
-
-The C<put_warmer()> method is used to create or update named warmers which
-are used to I<warm up> new segments in the index before they are exposed
-to user searches. For instance:
-
- $response = $e->indices->put_warmer(
- index => 'my_index',
- name => 'date_field_warmer',
- body => {
- sort => 'date'
- }
- );
-
-Query string parameters:
- C<allow_no_indices>,
- C<expand_wildcards>,
- C<ignore_unavailable>,
- C<master_timeout>,
- C<request_cache>
-
-See the L<put_warmer docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-warmers.html>
-for more information.
-
-=head2 C<get_warmer()>
-
- $response = $e->indices->get_warmer(
- index => 'index' | \@indices, # optional
- type => 'type' | \@types, # optional
- name => 'warmer' | \@warmers, # optional
- );
-
-The C<get_warmer()> method is used to retrieve warmers by name.
-
-Query string parameters:
- C<allow_no_indices>,
- C<expand_wildcards>,
- C<ignore_unavailable>,
- C<local>
-
-See the L<get_warmer docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-warmers.html>
-for more information.
-
-=head2 C<delete_warmer()>
-
- $response = $e->indices->get_warmer(
- index => 'index' | \@indices, # required
- name => 'warmer' | \@warmers, # required
- );
-
-The C<delete_warmer()> method is used to delete warmers by name.
-
-Query string parameters:
- C<master_timeout>
-
-See the L<delete_warmer docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-warmers.html>
-for more information.
=head1 STATS METHODS
@@ -760,15 +838,17 @@ Allowed metrics are:
C<request_cache>,
C<search>,
C<segments>,
- C<store>,
- C<warmer>
+ C<store>
+
Query string parameters:
C<completion_fields>,
+ C<error_trace>,
C<fielddata_fields>,
C<fields>,
C<groups>,
C<human>,
+ C<include_segment_file_sizes>,
C<level>,
C<types>
@@ -786,6 +866,7 @@ Provides insight into on-going shard recoveries.
Query string parameters:
C<active_only>,
C<detailed>,
+ C<error_trace>,
C<human>
See the L<recovery docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-recovery.html>
@@ -802,6 +883,7 @@ that an index contains.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<human>,
C<ignore_unavailable>,
@@ -821,6 +903,7 @@ copies of which shards, whether the shards are allocated or not.
Query string parameters:
C<allow_no_indices>,
+ C<error_trace>,
C<expand_wildcards>,
C<human>,
C<ignore_unavailable>,
@@ -844,16 +927,8 @@ with a particular index or field - and returns the tokens. Very useful
for debugging analyzer configurations.
Query string parameters:
- C<analyzer>,
- C<attributes>,
- C<char_filter>,
- C<explain>,
- C<field>,
- C<filter>,
- C<format>,
- C<prefer_local>,
- C<text>,
- C<tokenizer>
+ C<error_trace>,
+ C<human>
See the L<analyze docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html>
for more information.
@@ -872,37 +947,22 @@ whether the query is valid or not. Most useful when C<explain> is set
to C<true>, in which case it includes an execution plan in the output.
Query string parameters:
+ C<all_shards>,
C<allow_no_indices>,
C<analyze_wildcard>,
C<analyzer>,
C<default_operator>,
C<df>,
+ C<error_trace>,
C<explain>,
C<expand_wildcards>,
C<ignore_unavailable>,
C<lenient>,
- C<lowercase_expanded_terms>
C<q>,
C<rewrite>
See the L<validate_query docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-validate.html>
for more information.
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
-
-# ABSTRACT: A client for running index-level requests
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Ingest.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Ingest.pm
new file mode 100644
index 0000000..be9d6d6
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Ingest.pm
@@ -0,0 +1,130 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Client::7_0::Direct::Ingest;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+__PACKAGE__->_install_api('ingest');
+
+1;
+
+__END__
+
+# ABSTRACT: A client for accessing the Ingest API
+
+=head1 DESCRIPTION
+
+This module provides methods to access the Ingest API, such as creating,
+getting, deleting and simulating ingest pipelines.
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 METHODS
+
+=head2 C<put_pipeline()>
+
+ $response = $e->ingest->put_pipeline(
+ id => $id, # required
+ body => { pipeline defn } # required
+ );
+
+The C<put_pipeline()> method creates or updates a pipeline with the specified ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<put pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/put-pipeline-api.html>
+for more information.
+
+=head2 C<get_pipeline()>
+
+ $response = $e->ingest->get_pipeline(
+ id => \@id, # optional
+ );
+
+The C<get_pipeline()> method returns pipelines with the specified IDs (or all pipelines).
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<get pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-pipeline-api.html>
+for more information.
+
+=head2 C<delete_pipeline()>
+
+ $response = $e->ingest->delete_pipeline(
+ id => $id, # required
+ );
+
+The C<delete_pipeline()> method deletes the pipeline with the specified ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<delete pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/delete-pipeline-api.html>
+for more information.
+
+=head2 C<simulate()>
+
+ $response = $e->ingest->put_pipeline(
+ id => $id, # optional
+ body => { simulate args } # required
+ );
+
+The C<simulate()> method executes the pipeline specified by ID or inline in the body
+against the docs provided in the body and provides debugging output of the execution
+process.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<verbose>
+
+See the L<simulate pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/simulate-pipeline-api.html>
+for more information.
+
+
+=head2 C<simulate()>
+
+ $response = $e->ingest->put_pipeline(
+ id => $id, # optional
+ body => { simulate args } # required
+ );
+
+The C<simulate()> method executes the pipeline specified by ID or inline in the body
+against the docs provided in the body and provides debugging output of the execution
+process.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<verbose>
+
+See the L<simulate pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/simulate-pipeline-api.html>
+for more information.
+
+=head2 C<processor_grok>
+
+ $response = $e->inges->processor_grok()
+
+Retrieves the configured patterns associated with the Grok processor.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<grok processor docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/grok-processor.html>
+for more information.
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/License.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/License.pm
new file mode 100644
index 0000000..a1e3d31
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/License.pm
@@ -0,0 +1,134 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::License;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('license');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing License API for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->license->get();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<license>
+namespace, to support the API for the License plugin for Elasticsearch.
+
+=head1 METHODS
+
+The full documentation for the License plugin is available here:
+L<https://www.elastic.co/guide/en/x-pack/current/license-management.html>
+
+=head2 C<get()>
+
+ $response = $es->license->get()
+
+The C<get()> method returns the currently installed license.
+
+See the L<license.get docs|https://www.elastic.co/guide/en/x-pack/current/listing-licenses.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<local>
+
+=head2 C<post()>
+
+ $response = $es->license->post(
+ body => {...} # required
+ );
+
+The C<post()> method adds or updates the license for the cluster. The C<body>
+can be passed as JSON or as a string.
+
+See the L<license.put docs|https://www.elastic.co/guide/en/x-pack/current/installing-license.html>
+for more information.
+
+Query string parameters:
+ C<acknowledge>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_basic_status()>
+
+ $response = $es->license->get_basic_status()
+
+This API enables you to check the status of your basic license.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<get-basic-status docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-basic-status.html> for more.
+
+=head2 C<post_start_basic()>
+
+ $response = $es->license->post_start_basic()
+
+This API enables you to initiate an indefinite basic license, which gives access to all the basic features.
+
+Query string parameters:
+ C<acknowledge>,
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<post-start-basic docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/start-basic.html> for more.
+
+
+=head2 C<get_trial_status()>
+
+ $response = $es->license->get_trial_status()
+
+This API enables you to check the status of your trial license.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<get-trial-status docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-trial-status.html> for more.
+
+=head2 C<post_start_trial()>
+
+ $response = $es->license->post_start_trial()
+
+This API enables you to upgrade from a basic license to a 30-day trial license, which gives
+access to the platinum features.
+
+Query string parameters:
+ C<acknowledge>,
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<post-start-trial docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/start-trial.html> for more.
+
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/ML.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/ML.pm
new file mode 100644
index 0000000..296e75d
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/ML.pm
@@ -0,0 +1,842 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::ML;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('ml');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing ML API for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->ml->start_datafeed(...)
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with an C<ml>
+namespace, to support the
+L<Machine Learning APIs|https://www.elastic.co/guide/en/x-pack/7.0/xpack-ml.html>.
+
+The full documentation for the ML feature is available here:
+L<https://www.elastic.co/guide/en/x-pack/7.0/xpack-ml.html>
+
+=head1 DATAFEED METHODS
+
+=head2 C<put_datafeed()>
+
+ $response = $es->ml->put_datafeed(
+ datafeed_id => $id # required
+ body => {...} # required
+ )
+
+The C<put_datafeed()> method enables you to instantiate a datafeed.
+
+See the L<put_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_datafeed()>
+
+ $response = $es->xpack->ml->delete_datafeed(
+ datafeed_id => $id # required
+ )
+
+The C<delete_datafeed()> method enables you to delete a datafeed.
+
+See the L<delete_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<force>,
+ C<human>
+
+=head2 C<start_datafeed()>
+
+ $response = $es->ml->start_datafeed(
+ datafeed_id => $id # required
+ )
+
+The C<start_datafeed()> method enables you to start a datafeed.
+
+See the L<start_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-start-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<end>,
+ C<error_trace>,
+ C<human>,
+ C<start>,
+ C<timeout>
+
+=head2 C<stop_datafeed()>
+
+ $response = $es->ml->stop_datafeed(
+ datafeed_id => $id # required
+ )
+
+The C<stop_datafeed()> method enables you to stop a datafeed.
+
+See the L<stop_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-stop-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_datafeeds>,
+ C<error_trace>,
+ C<force>,
+ C<human>,
+ C<timeout>
+
+=head2 C<get_datafeeds()>
+
+ $response = $es->ml->get_datafeeds(
+ datafeed_id => $id # optional
+ )
+
+The C<get_datafeeds()> method enables you to retrieve configuration information for datafeeds.
+
+See the L<get_datafeeds docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_datafeeds>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_datafeed_stats()>
+
+ $response = $es->ml->get_datafeed_stats(
+ datafeed_id => $id # optional
+ )
+
+The C<get_datafeed_stats()> method enables you to retrieve configuration information for datafeeds.
+
+See the L<get_datafeed_stats docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-datafeed-stats.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_datafeeds>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<preview_datafeed()>
+
+ $response = $es->ml->preview_datafeed(
+ datafeed_id => $id # required
+ )
+
+The C<preview_datafeed()> method enables you to preview a datafeed.
+
+See the L<preview_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-preview-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<update_datafeed()>
+
+ $response = $es->ml->update_datafeed(
+ datafeed_id => $id # required
+ body => {...} # required
+ )
+
+The C<update_datafeed()> method enables you to update certain properties of a datafeed.
+
+See the L<update_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 JOB METHODS
+
+=head2 C<put_job()>
+
+ $response = $es->ml->put_job(
+ job_id => $id # required
+ body => {...} # required
+ )
+
+The C<put_job()> method enables you to instantiate a job.
+
+See the L<put_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-job.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_job()>
+
+ $response = $es->ml->delete_job(
+ job_id => $id # required
+ )
+
+The C<delete_job()> method enables you to delete a job.
+
+See the L<delete_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-job.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<force>,
+ C<human>,
+ C<wait_for_completion>
+
+=head2 C<open_job()>
+
+ $response = $es->ml->open_job(
+ job_id => $id # required
+ )
+
+The C<open_job()> method enables you to open a closed job.
+
+See the L<open_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-open-job.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<close_job()>
+
+ $response = $es->ml->close_job(
+ job_id => $id # required
+ )
+
+The C<close_job()> method enables you to close an open job.
+
+See the L<close_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-close-job.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_jobs>,
+ C<error_trace>,
+ C<force>,
+ C<human>,
+ C<timeout>
+
+=head2 C<get_jobs()>
+
+ $response = $es->ml->get_jobs(
+ job_id => $id # optional
+ )
+
+The C<get_jobs()> method enables you to retrieve configuration information for jobs.
+
+See the L<get_jobs docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_jobs>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_job_stats()>
+
+ $response = $es->ml->get_jobs_stats(
+ job_id => $id # optional
+ )
+
+The C<get_jobs_stats()> method enables you to retrieve usage information for jobs.
+
+See the L<get_job_statss docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job-stats.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_jobs>,
+ C<error_trace>,
+ C<human>
+
+
+=head2 C<flush_job()>
+
+ $response = $es->ml->flush_job(
+ job_id => $id # required
+ )
+
+The C<flush_job()> method forces any buffered data to be processed by the job.
+
+See the L<flush_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-flush-job.html>
+for more information.
+
+Query string parameters:
+ C<advance_time>,
+ C<calc_interm>,
+ C<end>,
+ C<error_trace>,
+ C<human>,
+ C<skip_time>,
+ C<start>
+
+=head2 C<post_data()>
+
+ $response = $es->ml->post_data(
+ job_id => $id # required
+ body => [data] # required
+ )
+
+The C<post_data()> method enables you to send data to an anomaly detection job for analysis.
+
+See the L<post_data docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-post-data.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<reset_end>,
+ C<reset_start>
+
+=head2 C<update_job()>
+
+ $response = $es->ml->update_job(
+ job_id => $id # required
+ body => {...} # required
+ )
+
+The C<update_job()> method enables you to update certain properties of a job.
+
+See the L<update_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-job.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_expired_data>
+
+ $response = $es->ml->delete_expired_data(
+ )
+
+The C<delete_expired_data()> method deletes expired machine learning data.
+
+See the L<delete_expired_data docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-expired-data.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+
+=head1 CALENDAR METHODS
+
+=head2 C<put_calendar()>
+
+ $response = $es->ml->put_calendar(
+ calendar_id => $id # required
+ body => {...} # optional
+ )
+
+The C<put_calendar()> method creates a new calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<put calendar docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-calendar.html>
+for more information.
+
+=head2 C<delete_calendar()>
+
+ $response = $es->ml->delete_calendar(
+ calendar_id => $id # required
+ )
+
+The C<delete_calendar()> method deletes the specified calendar
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<delete_calendar docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-calendar.html>
+for more information.
+
+=head2 C<put_calendar_job()>
+
+ $response = $es->ml->put_calendar_job(
+ calendar_id => $id, # required
+ job_id => $id # required
+ )
+
+The C<put_calendar_job()> method adds a job to a calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<put_calendar_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-calendar-job.html>
+for more information.
+
+=head2 C<delete_calendar_job()>
+
+ $response = $es->ml->delete_calendar_job(
+ calendar_id => $id, # required
+ job_id => $id # required
+ )
+
+The C<delete_calendar_job()> method deletes a job from a calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<delete_calendar_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-calendar-job.html>
+for more information.
+
+=head2 C<put_calendar_event()>
+
+ $response = $es->ml->post_calendar_events(
+ calendar_id => $id, # required
+ body => {...} # required
+ )
+
+The C<post_calendar_events()> method adds scheduled events to a calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<post_calendar_events docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-post-calendar-events.html>
+for more information.
+
+
+=head2 C<delete_calendar_event()>
+
+ $response = $es->ml->delete_calendar_event(
+ calendar_id => $id, # required
+ event_id => $id # required
+ )
+
+The C<delete_calendar_event()> method deletes an event from a calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<delete_calendar_event docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-calendar-event.html>
+for more information.
+
+=head2 C<get_calendars()>
+
+ $response = $es->ml->get_calendars(
+ calendar_id => $id, # optional
+ )
+
+The C<get_calendars()> method returns the specified calendar or all calendars.
+
+Query string parameters:
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<size>
+
+See the L<get_calendars docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-calendar-event.html>
+for more information.
+
+=head2 C<get_calendar_events()>
+
+ $response = $es->ml->get_calendar_events(
+ calendar_id => $id, # required
+ )
+
+The C<get_calendar_events()> method retrieves events from a calendar.
+
+Query string parameters:
+ C<end>,
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<job_id>,
+ C<size>,
+ C<start>
+
+See the L<get_calendar_events docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-calendar-event.html>
+for more information.
+
+=head1 FILTER METHODS
+
+=head2 C<put_filter()>
+
+ $response = $es->ml->put_filter(
+ filter_id => $id, # required
+ body => {...} # required
+ )
+
+The C<put_filter()> method creates a named filter.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<put_filter docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-filter.html>
+for more information.
+
+=head2 C<update_filter()>
+
+ $response = $es->ml->update_filter(
+ filter_id => $id, # required
+ body => {...} # required
+ )
+
+The C<update_filter()> method updates the description of a filter, adds items, or removes items.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<update_filter docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-filter.html>
+for more information.
+
+=head2 C<get_filters()>
+
+ $response = $es->ml->get_filters(
+ filter_id => $id, # optional
+ )
+
+The C<get_filters()> method retrieves a named filter or all filters.
+
+Query string parameters:
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<size>
+
+See the L<get_filters docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-filters.html>
+for more information.
+
+=head2 C<delete_filter()>
+
+ $response = $es->ml->delete_filter(
+ filter_id => $id, # required
+ )
+
+The C<delete_filter()> method deletes a named filter.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<delete_filters docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-filter.html>
+for more information.
+
+=head1 FORECAST METHODS
+
+=head2 C<forecast()>
+
+ $response = $es->ml->forecast(
+ job_id => $id # required
+ )
+
+The C<forecast()> method enables you to create a new forecast
+
+See the L<forecast docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-forecast.html>
+for more information.
+
+Query string parameters:
+ C<duration>,
+ C<error_trace>,
+ C<expires_in>,
+ C<human>
+
+=head2 C<delete_forecast()>
+
+ $response = $es->ml->delete_forecast(
+ forecast_id => $id, # required
+ job_id => $id # required
+ )
+
+The C<delete_forecast()> method enables you to delete an existing forecast.
+
+See the L<delete_forecast docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-forecast.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_forecasts>,
+ C<error_trace>,
+ C<human>,
+ C<timeout>
+
+=head1 MODEL SNAPSHOT METHODS
+
+=head2 C<delete_model_snapshot()>
+
+ $response = $es->ml->delete_model_snapshot(
+ snapshot_id => $id # required
+ )
+
+The C<delete_model_snapshot()> method enables you to delete an existing model snapshot.
+
+See the L<delete_model_snapshot docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-snapshot.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_model_snapshots()>
+
+ $response = $es->ml->get_model_snapshots(
+ job_id => $job_id, # required
+ snapshot_id => $snapshot_id # optional
+ )
+
+The C<get_model_snapshots()> method enables you to retrieve information about model snapshots.
+
+See the L<get_model_snapshots docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-snapshot.html>
+for more information.
+
+Query string parameters:
+ C<desc>,
+ C<end>,
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<size>,
+ C<sort>,
+ C<start>
+
+=head2 C<revert_model_snapshot()>
+
+ $response = $es->ml->revert_model_snapshot(
+ job_id => $job_id, # required
+ snapshot_id => $snapshot_id # required
+ )
+
+The C<revert_model_snapshots()> method enables you to revert to a specific snapshot.
+
+See the L<revert_model_snapshot docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-revert-snapshot.html>
+for more information.
+
+Query string parameters:
+ C<delete_intervening_results>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<update_model_snapshot()>
+
+ $response = $es->ml->update_model_snapshot(
+ job_id => $job_id, # required
+ snapshot_id => $snapshot_id # required
+ )
+
+The C<update_model_snapshots()> method enables you to update certain properties of a snapshot.
+
+See the L<update_model_snapshot docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-snapshot.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 RESULT METHODS
+
+=head2 C<get_buckets()>
+
+ $response = $es->ml->get_buckets(
+ job_id => $job_id, # required
+ timestamp => $timestamp # optional
+ )
+
+The C<get_buckets()> method enables you to retrieve job results for one or more buckets.
+
+See the L<get_buckets docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-bucket.html>
+for more information.
+
+Query string parameters:
+ C<anomaly_score>,
+ C<desc>,
+ C<end>,
+ C<error_trace>,
+ C<exclude_interim>,
+ C<expand>,
+ C<from>,
+ C<human>,
+ C<size>,
+ C<sort>,
+ C<start>
+
+=head2 C<get_overall_buckets()>
+
+ $response = $es->ml->get_overall_buckets(
+ job_id => $job_id, # required
+ )
+
+The C<get_overall_buckets()> method retrieves overall bucket results that summarize the bucket results of multiple jobs.
+
+See the L<get_overall_buckets docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-overall-buckets.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_jobs>,
+ C<bucket_span>,
+ C<end>,
+ C<error_trace>,
+ C<exclude_interim>,
+ C<human>,
+ C<overall_score>,
+ C<start>,
+ C<top_n>
+
+=head2 C<get_categories()>
+
+ $response = $es->ml->get_categories(
+ job_id => $job_id, # required
+ category_id => $category_id # optional
+ )
+
+The C<get_categories()> method enables you to retrieve job results for one or more categories.
+
+See the L<get_categories docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-category.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<size>
+
+
+=head2 C<get_influencers()>
+
+ $response = $es->ml->get_influencers(
+ job_id => $job_id, # required
+ )
+
+The C<get_influencers()> method enables you to retrieve job results for one or more influencers.
+
+See the L<get_influencers docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-influencer.html>
+for more information.
+
+Query string parameters:
+ C<desc>,
+ C<end>,
+ C<error_trace>,
+ C<exclude_interim>,
+ C<expand>,
+ C<from>,
+ C<human>,
+ C<influencer_score>,
+ C<size>,
+ C<sort>,
+ C<start>
+
+=head2 C<get_records()>
+
+ $response = $es->ml->get_records(
+ job_id => $job_id, # required
+ )
+
+The C<get_records()> method enables you to retrieve anomaly records for a job.
+
+See the L<get_records docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-record.html>
+for more information.
+
+Query string parameters:
+ C<desc>,
+ C<end>,
+ C<error_trace>,
+ C<exclude_interim>,
+ C<expand>,
+ C<from>,
+ C<human>,
+ C<record_score>,
+ C<size>,
+ C<sort>,
+ C<start>
+
+=head1 FILE STRUCTURE METHODS
+
+=head2 C<find_file_structure>
+
+
+ $response = $es->ml->find_file_structure(
+ body => { ... }, # required
+ )
+
+The C<find_file_structure()> method finds the structure of a text file which contains data
+that is suitable to be ingested into Elasticsearch.
+
+See the L<find_file_structure docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-find-file-structure.html>
+for more information.
+
+Query string parameters:
+ C<charset>,
+ C<column_names>,
+ C<delimiter>,
+ C<error_trace>,
+ C<explain>,
+ C<format>,
+ C<grok_pattern>,
+ C<has_header_row>,
+ C<human>,
+ C<lines_to_sample>,
+ C<quote>,
+ C<should_trim_fields>,
+ C<timeout>,
+ C<timestamp_field>,
+ C<timestamp_format>
+
+
+
+=head1 INFO METHODS
+
+
+=head2 C<info>
+
+ $response = $es->ml->info();
+
+The C<info()> method returns defaults and limits used by machine learning.
+
+See the L<find_file_structure docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-ml-info.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 UPGRADE METHODS
+
+=head2 C<set_upgrade_mode>
+
+ $response = $es->ml->set_upgrade_mode();
+
+The C<set_upgrade_mode()> method sets a cluster wide C<upgrade_mode> setting that prepares
+machine learning indices for an upgrade.
+
+See the L<set_upgrade_mode docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-set-upgrade-mode.html>
+for more information.
+
+Query string parameters:
+ C<enabled>,
+ C<error_trace>,
+ C<human>,
+ C<timeout>
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Migration.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Migration.pm
new file mode 100644
index 0000000..557ed83
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Migration.pm
@@ -0,0 +1,102 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Migration;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('migration');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Migration API for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->migration->deprecations();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<migration>
+namespace, to support the API
+L<Migration APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api.html>.
+
+=head1 METHODS
+
+The full documentation for the Migration APIs is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api.html>
+
+=head2 C<deprecations()>
+
+ $response = $es->migration->deprecations(
+ index => $index # optional
+ )
+
+The C<deprecations()> API is to be used to retrieve information about different cluster, node,
+and index level settings that use deprecated features that will be removed or changed in the
+next major version.
+
+See the L<deprecations docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-deprecation.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_assistance()>
+
+ $response = $es->migration->get_assistance(
+ index => $index | \@indices # optional
+ )
+
+The C<get_assistance()> API analyzes existing indices in the cluster and returns the information
+about indices that require some changes before the cluster can be upgraded to the next major version.
+
+See the L<get_assistance docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-assistance.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>
+
+=head2 C<upgrade()>
+
+ $response = $es->migration->upgrade(
+ index => $index # required
+ )
+
+The C<upgrade()> API performs the upgrade of internal indices to make them compatible with the
+next major version.
+
+See the L<upgrade() docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-upgrade.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<wait_for_completion>
+
+
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Monitoring.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Monitoring.pm
new file mode 100644
index 0000000..8ec1fed
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Monitoring.pm
@@ -0,0 +1,35 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Monitoring;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('monitoring');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Monitoring for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->monitoring( body => {...} )
\ No newline at end of file
diff --git a/lib/Search/Elasticsearch/Client/2_0/Direct/Nodes.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Nodes.pm
similarity index 60%
rename from lib/Search/Elasticsearch/Client/2_0/Direct/Nodes.pm
rename to lib/Search/Elasticsearch/Client/7_0/Direct/Nodes.pm
index 78d4b8e..17aef05 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Direct/Nodes.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Nodes.pm
@@ -1,23 +1,32 @@
-package Search::Elasticsearch::Client::2_0::Direct::Nodes;
-$Search::Elasticsearch::Client::2_0::Direct::Nodes::VERSION = '6.81';
+# 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 Search::Elasticsearch::Client::7_0::Direct::Nodes;
+
use Moo;
-with 'Search::Elasticsearch::Client::2_0::Role::API';
+with 'Search::Elasticsearch::Client::7_0::Role::API';
with 'Search::Elasticsearch::Role::Client::Direct';
__PACKAGE__->_install_api('nodes');
1;
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Direct::Nodes - A client for running node-level requests
-
-=head1 VERSION
+__END__
-version 6.81
+# ABSTRACT: A client for running node-level requests
=head1 DESCRIPTION
@@ -28,6 +37,7 @@ It does L<Search::Elasticsearch::Role::Client::Direct>.
=head1 METHODS
+
=head2 C<info()>
$response = $e->nodes->info(
@@ -52,6 +62,7 @@ Allowed metrics are:
C<transport>
Query string parameters:
+ C<error_trace>,
C<flat_settings>,
C<human>
@@ -79,6 +90,7 @@ Allowed metrics are:
C<breaker>,
C<fs>,
C<http>,
+ C<include_segment_file_sizes>,
C<indices>,
C<jvm>,
C<network>,
@@ -89,11 +101,11 @@ Allowed metrics are:
C<transport>
If the C<indices> metric (or C<_all>) is specified, then
-L<indices_stats|Search::Elasticsearch::Client::2_0::Direct::Indices/indices_stats()>
+L<indices_stats|Search::Elasticsearch::Client::7_0::Direct::Indices/indices_stats()>
information is returned on a per-node basis. Which indices stats are
returned can be controlled with the C<index_metric> parameter:
- $response = $e->cluster->node_stats(
+ $response = $e->nodes->stats(
node_id => 'node_1',
metric => ['indices','fs']
index_metric => ['docs','fielddata']
@@ -118,8 +130,10 @@ Allowed index metrics are:
C<store>,
C<warmer>
+
Query string parameters:
C<completion_fields>,
+ C<error_trace>,
C<fielddata_fields>,
C<fields>,
C<groups>,
@@ -127,7 +141,7 @@ Query string parameters:
C<level>,
C<types>
-See the L<node_stats docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html>
+See the L<stats docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html>
for more information.
=head2 C<hot_threads()>
@@ -140,6 +154,8 @@ The C<hot_threads()> method is a useful tool for diagnosing busy nodes. It
takes a snapshot of which threads are consuming the most CPU.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<ignore_idle_threads>,
C<interval>,
C<snapshots>,
@@ -150,21 +166,38 @@ Query string parameters:
See the L<hot_threads docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-hot-threads.html>
for more information.
-=head1 AUTHOR
+=head2 C<reload_secure_settings()>
+
+ $response = $e->nodes->reload_secure_settings(
+ node_id => $node_id | \@node_ids # optional
+ );
-Enrico Zimuel <enrico.zimuel@elastic.co>
+The C<reload_secure_settings()> API will reload the reloadable settings stored in the keystore
+on each node.
-=head1 COPYRIGHT AND LICENSE
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<timeout>
-This software is Copyright (c) 2020 by Elasticsearch BV.
+See the L<reload-secure-settings docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/secure-settings.html>
+for more information.
-This is free software, licensed under:
+=head2 C<usage()>
- The Apache License, Version 2.0, January 2004
+ $response = $e->nodes->usage(
+ node_id => $node_id | \@node_ids # optional
+ metric => $metric | \@metrics # optional
+ )
-=cut
+The C<usage()> API retrieve sinformation on the usage of features for each node.
-__END__
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<timeout>
-# ABSTRACT: A client for running node-level requests
+See the L<nodes_usage docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-usage.html>
+for more information.
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Rollup.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Rollup.pm
new file mode 100644
index 0000000..53021dd
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Rollup.pm
@@ -0,0 +1,186 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Rollup;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('rollup');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Rollups for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->rollup->search( body => {...} )
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<rollup>
+namespace, to support the
+L<Rollup APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-apis.html>.
+
+The full documentation for the Rollups feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-rollup.html>
+
+
+=head1 GENERAL METHODS
+
+=head2 C<search()>
+
+ $response = $es->rollup->search(
+ index => $index | \@indices, # optional
+ body => {...} # optional
+ )
+
+The C<search()> method executes a normal search but can join the results from ordinary indices with
+those from rolled up indices.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<typed_keys>
+
+See the L<rollup search docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-search.html>
+for more information.
+
+=head1 JOB METHODS
+
+=head2 C<put_job()>
+
+ $response = $es->rollup->put_job(
+ id => $id, # required
+ body => {...} # optional
+ )
+
+The C<put_job()> method creates a rollup job which will rollup matching indices to a rolled up index
+in the background.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup create job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-put-job.html>
+for more information.
+
+=head2 C<delete_job()>
+
+ $response = $es->rollup->delete_job(
+ id => $id, # required
+ )
+
+The C<delete_job()> method deletes a rollup job by ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup delete job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-delete-job.html>
+for more information.
+
+=head2 C<get_jobs()>
+
+ $response = $es->rollup->get_jobs(
+ id => $id, # optional
+ )
+
+The C<get_job()> method retrieves a rollup job by ID, or all jobs if not specified.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup get jobs docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-get-job.html>
+for more information.
+
+=head2 C<start_job()>
+
+ $response = $es->rollup->start_job(
+ id => $id, # required
+ )
+
+The C<start_job()> method starts the specified rollup job.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup start job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-start-job.html>
+for more information.
+
+=head2 C<stop_job()>
+
+ $response = $es->rollup->stop_job(
+ id => $id, # required
+ )
+
+The C<stop_job()> method stops the specified rollup job.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup stop job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-stop-job.html>
+for more information.
+
+=head1 DATA METHODS
+
+=head2 C<get_rollup_caps()>
+
+ $response = $es->rollup->get_rollup_caps(
+ id => $index # optional
+ )
+
+The C<get_rollup_caps()> method returns the capabilities of any rollup jobs that have been configured for a specific index or index pattern.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<get rollup caps docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-get-rollup-caps.html>
+for more information.
+
+=head2 C<get_rollup_index_caps()>
+
+ $response = $es->rollup->get_rollup_index_caps(
+ id => $index # optional
+ )
+
+The C<get_rollup_index_caps()> method returns the rollup capabilities of all jobs inside of a rollup index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<get rollup index caps docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-get-rollup-index-caps.html>
+for more information.
+
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/SQL.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/SQL.pm
new file mode 100644
index 0000000..a8a0140
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/SQL.pm
@@ -0,0 +1,96 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::SQL;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('sql');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing SQL for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->sql->query( body => {...} )
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with an C<sql>
+namespace, to support the
+L<SQL APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest.html>.
+
+The full documentation for the SQL feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-sql.html>
+
+=head1 GENERAL METHODS
+
+=head2 C<query()>
+
+ $response = $es->sql->query(
+ body => {...} # required
+ )
+
+The C<query()> method executes an SQL query and returns the results.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<format>,
+ C<human>
+
+See the L<query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest.html>
+for more information.
+
+=head2 C<translate()>
+
+ $response = $es->sql->translate(
+ body => {...} # required
+ )
+
+The C<translate()> method takes an SQL query and returns the query DSL which would be executed.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<translate docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-translate.html>
+for more information.
+
+=head2 C<clear_cursor()>
+
+ $response = $es->sql->clear_cursor(
+ body => {...} # required
+ )
+
+The C<clear_cursor()> method cleans up an ongoing scroll request.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest.html>
+for more information.
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/SSL.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/SSL.pm
new file mode 100644
index 0000000..34233cb
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/SSL.pm
@@ -0,0 +1,49 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::SSL;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('ssl');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing SSL for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->ssl->certificates()
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with an C<ssl>
+namespace, to support the
+L<SSL APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-ssl.html>.
+
+=head1 GENERAL METHODS
+
+=head2 C<certificates()>
+
+ $response = $es->ssl->certificates()
+
+The C<certificates()> method returns all the certificate information on a single node of Elasticsearch.
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/SearchableSnapshots copy.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/SearchableSnapshots copy.pm
new file mode 100644
index 0000000..28cdcf1
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/SearchableSnapshots copy.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::SearchableSnapshots;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('searchable_snapshots');
+
+1;
+
+__END__
+
+# ABSTRACT: Searchable Snapshots feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Searchable Snapshots feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/searchable-snapshots-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->searchable_snapshots->repository_stats(
+ 'repository' => $repository
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Security.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Security.pm
new file mode 100644
index 0000000..8cc98b4
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Security.pm
@@ -0,0 +1,461 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Security;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('security');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Security API for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->security->authenticate();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<security>
+namespace, to support the
+L<Security APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api.html>.
+
+The full documentation for the Security feature is available here:
+L<https://www.elastic.co/guide/en/x-pack/current/xpack-security.html>
+
+=head1 GENERAL METHODS
+
+=head2 C<authenticate()>
+
+ $response = $es->security->authenticate()
+
+The C<authenticate()> method checks that the C<userinfo> is correct and returns
+a list of which roles are assigned to the user.
+
+See the L<authenticate docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-authenticate.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<clear_cached_realms()>
+
+ $response = $es->security->clear_cached_realms(
+ realms => $realms # required (comma-separated string)
+ );
+
+The C<clear_cached_realms()> method clears the caches for the specified realms
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<usernames>
+
+See the L<clear_cached_realms docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-cache.html>
+for more information.
+
+
+=head1 USER METHODS
+
+=head2 C<put_user()>
+
+ $response = $es->security->put_user(
+ username => $username, # required
+ body => {...} # required
+ );
+
+The C<put_user()> method creates a new user or updates an existing user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_user()>
+
+ $response = $es->security->get_user(
+ username => $username | \@usernames # optional
+ );
+
+The C<get_user()> method retrieves info for the specified users (or all users).
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_user()>
+
+ $response = $es->security->delete_user(
+ username => $username # required
+ );
+
+The C<delete_user()> method deletes the specified user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<change_password()>
+
+ $response = $es->security->change_password(
+ username => $username # optional
+ body => {
+ password => $password # required
+ }
+ )
+
+The C<change_password()> method changes the password for the specified user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+=head2 C<disable_user()>
+
+ $response = $es->security->disable_user(
+ username => $username # required
+ );
+
+The C<disable_user()> method disables the specified user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<enable_user()>
+
+ $response = $es->security->enable_user(
+ username => $username # required
+ );
+
+The C<enable_user()> method enables the specified user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 ROLE METHODS
+
+=head2 C<put_role()>
+
+ $response = $es->security->put_role(
+ name => $name, # required
+ body => {...} # required
+ );
+
+The C<put_role()> method creates a new role or updates an existing role.
+
+See the L<Role Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-roles.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_role()>
+
+ $response = $es->security->get_role(
+ name => $name | \@names # optional
+ );
+
+The C<get_role()> method retrieves info for the specified roles (or all roles).
+
+See the L<Role Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-roles.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_role()>
+
+ $response = $es->security->delete_role(
+ name => $name # required
+ );
+
+The C<delete_role()> method deletes the specified role.
+
+See the L<Role Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-roles.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<clear_cached_roles()>
+
+ $response = $es->security->clear_cached_roles(
+ names => $names # required (comma-separated string)
+ );
+
+The C<clear_cached_roles()> method clears the caches for the specified roles.
+
+See the L<Role Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-roles.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+
+=head1 ROLE MAPPING METHODS
+
+=head2 C<put_role_mapping()>
+
+ $response = $es->security->put_role_mapping(
+ name => $name, # required
+ body => {...} # required
+ );
+
+The C<put_role_mapping()> method creates a new role mapping or updates an existing role mapping.
+
+See the L<Role Mapping docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-role-mapping.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_role_mapping()>
+
+ $response = $es->security->get_role_mapping(
+ name => $name, # optional
+ );
+
+The C<get_role_mapping()> method retrieves one or more role mappings.
+
+See the L<Role Mapping docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-role-mapping.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_role_mapping()>
+
+ $response = $es->security->delete_role_mapping(
+ name => $name, # required
+ );
+
+The C<delete_role_mapping()> method deletes a role mapping.
+
+See the L<Role Mapping docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-role-mapping.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 TOKEN METHODS
+
+=head2 C<get_token()>
+
+ $response = $es->security->get_token(
+ body => {...} # required
+ );
+
+The C<get_token()> method enables you to create bearer tokens for access without
+requiring basic authentication.
+
+See the L<Token Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-tokens.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<invalidate_token()>
+
+ $response = $es->security->invalidate_token(
+ body => {...} # required
+ );
+
+The C<invalidate_token()> method enables you to invalidate bearer tokens for access without
+requiring basic authentication.
+
+See the L<Token Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-tokens.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 API KEY METHODS
+
+=head2 C<create_api_key()>
+
+ $response = $es->security->create_api_key(
+ body => {...} # required
+ )
+
+The C<create_api_key()> API is used to create API keys which can be used for access instead
+of basic authentication.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<refresh>
+
+See the L<Create API Key docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html> for more.
+
+=head2 C<get_api_key()>
+
+ $response = $es->security->get_api_key(
+ id => $id, # optional
+ name => $name, # optional
+ realm_name => $realm, # optional
+ username => $username # optional
+ )
+
+The C<get_api_key()> API is used to get information about an API key.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<id>,
+ C<name>,
+ C<realm_name>,
+ C<username>
+
+See the L<Get API Key docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-api-key.html> for more.
+
+=head2 C<invalidate_api_key()>
+
+ $response = $es->security->invalidate_api_key(
+ id => $id, # optional
+ name => $name, # optional
+ realm_name => $realm, # optional
+ username => $username # optional
+ )
+
+The C<invalidate_api_key()> API is used to invalidate an API key.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<id>,
+ C<name>,
+ C<realm_name>,
+ C<username>
+
+See the L<Invalidate API Key docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-invalidate-api-key.html> for more.
+
+=head1 USER PRIVILEGE METHODS
+
+=head2 C<get_user_privileges()>
+
+ $response = $es->get_user_privileges();
+
+ The C<get_user_privileges()> method retrieves the privileges granted to the current user.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+=head2 C<has_privileges()>
+ $response = $es->has_privileges(
+ user => $user, # optional
+ body => {...} # required
+ );
+
+ The C<has_privileges()> method checks whether the current or specified user has the listed privileges.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<Has Privileges docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-has-privileges.html> for more.
+
+
+=head1 APPLICATION PRIVILEGE METHODS
+
+=head2 C<put_privileges()>
+
+ $response = $es->put_privileges(
+ application => $application, # required
+ name => $name, # required
+ body => {...} # required
+ );
+
+ The C<put_privileges()> method creates or updates the named privilege for a particular application.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<refresh>
+
+See the L<Create or Update Application Privileges docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-privileges.html> for more.
+
+=head2 C<get_privileges()>
+
+ $response = $es->get_privileges(
+ application => $application, # required
+ name => $name, # required
+ );
+
+ The C<get_privileges()> method retrieves the named privilege for a particular application.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<Get Application Privileges docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-privileges.html> for more.
+
+=head2 C<delete_privileges()>
+
+ $response = $es->delete_privileges(
+ application => $application, # required
+ name => $name, # required
+ );
+
+ The C<delete_privileges()> method deletes the named privilege for a particular application.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<refresh>
+
+See the L<Delete Application Privileges docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-privilege.html> for more.
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Slm.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Slm.pm
new file mode 100644
index 0000000..ebca867
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Slm.pm
@@ -0,0 +1,43 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Slm;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('slm');
+
+1;
+
+__END__
+
+# ABSTRACT: Snapshot lifecycle management feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Snapshot lifecycle management feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-lifecycle-management-api.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->slm->get_status();
+
diff --git a/lib/Search/Elasticsearch/Client/2_0/Direct/Snapshot.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Snapshot.pm
similarity index 85%
rename from lib/Search/Elasticsearch/Client/2_0/Direct/Snapshot.pm
rename to lib/Search/Elasticsearch/Client/7_0/Direct/Snapshot.pm
index c29f813..1a0bf5a 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Direct/Snapshot.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Snapshot.pm
@@ -1,23 +1,19 @@
-package Search::Elasticsearch::Client::2_0::Direct::Snapshot;
-$Search::Elasticsearch::Client::2_0::Direct::Snapshot::VERSION = '6.81';
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Client::7_0::Direct::Snapshot;
+
use Moo;
-with 'Search::Elasticsearch::Client::2_0::Role::API';
+with 'Search::Elasticsearch::Client::7_0::Role::API';
with 'Search::Elasticsearch::Role::Client::Direct';
__PACKAGE__->_install_api('snapshot');
1;
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Direct::Snapshot - A client for managing snapshot/restore
-
-=head1 VERSION
+__END__
-version 6.81
+# ABSTRACT: A client for managing snapshot/restore
=head1 DESCRIPTION
@@ -29,6 +25,7 @@ It does L<Search::Elasticsearch::Role::Client::Direct>.
=head1 METHODS
+
=head2 C<create_repository()>
$e->snapshot->create_repository(
@@ -39,6 +36,8 @@ It does L<Search::Elasticsearch::Role::Client::Direct>.
Create a repository for backups.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<timeout>,
C<verify>
@@ -55,6 +54,8 @@ for more information.
Retrieve existing repositories.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<local>,
C<master_timeout>
@@ -70,6 +71,8 @@ for more information.
Verify existing repository.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<timeout>
@@ -85,6 +88,8 @@ for more information.
Delete repositories by name.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<timeout>
@@ -104,6 +109,8 @@ Create a snapshot of the whole cluster or individual indices in the named
repository.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<wait_for_completion>
@@ -117,7 +124,11 @@ Query string parameters:
Retrieve snapshots in the named repository.
Query string parameters:
- C<master_timeout>
+ C<error_trace>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<verbose>
See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
for more information.
@@ -132,11 +143,14 @@ for more information.
Delete snapshot in the named repository.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>
See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
for more information.
+
=head2 C<restore()>
$e->snapshot->restore(
@@ -149,6 +163,8 @@ for more information.
Restore a named snapshot.
Query string parameters:
+ C<error_trace>,
+ C<human>,
C<master_timeout>,
C<wait_for_completion>
@@ -165,26 +181,10 @@ for more information.
Returns status information about the specified snapshots.
Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<ignore_unavailable>,
C<master_timeout>
See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
for more information.
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
-
-# ABSTRACT: A client for managing snapshot/restore
-
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Tasks.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Tasks.pm
new file mode 100644
index 0000000..a12cce7
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Tasks.pm
@@ -0,0 +1,97 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Tasks;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+__PACKAGE__->_install_api('tasks');
+
+1;
+
+__END__
+
+# ABSTRACT: A client for accessing the Task Management API
+
+=head1 DESCRIPTION
+
+This module provides methods to access the Task Management API, such as listing
+tasks and cancelling tasks.
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 METHODS
+
+=head2 C<list()>
+
+ $response = $e->tasks->list(
+ task_id => $task_id # optional
+ );
+
+The C<list()> method returns all running tasks or, if a C<task_id> is specified, info
+about that task.
+
+Query string parameters:
+ C<actions>,
+ C<detailed>,
+ C<error_trace>,
+ C<group_by>,
+ C<human>,
+ C<nodes>,
+ C<parent_task_id>,
+ C<timeout>,
+ C<wait_for_completion>
+
+See the L<task management docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
+for more information.
+
+=head2 C<get()>
+
+ $response = $e->tasks->get(
+ task_id => $task_id # required
+ );
+
+The C<get()> method returns the task with the specified ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<wait_for_completion>
+
+See the L<task management docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
+for more information.
+
+=head2 C<cancel()>
+
+ $response = $e->tasks->cancel(
+ task_id => $task_id # required
+ );
+
+The C<cancel()> method attempts to cancel the specified C<task_id> or multiple tasks.
+
+Query string parameters:
+ C<actions>,
+ C<error_trace>,
+ C<human>,
+ C<nodes>,
+ C<parent_task_id>,
+ C<timeout>
+
+See the L<task management docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
+for more information.
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Transform.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Transform.pm
new file mode 100644
index 0000000..68ef244
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Transform.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Transform;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('transform');
+
+1;
+
+__END__
+
+# ABSTRACT: Transform feature of Search::Elasticsearch 7.x
+
+=head2 DESCRIPTION
+
+The full documentation for Transform feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/transform-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->transform->get_transform(
+ 'transform_id' => $transform_id
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/Watcher.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/Watcher.pm
new file mode 100644
index 0000000..973d292
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/Watcher.pm
@@ -0,0 +1,225 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::Watcher;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('watcher');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Watcher API for Search::Elasticsearch 7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->watcher->start();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<watcher>
+namespace, to support the
+L<Watcher APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api.html>.
+
+=head1 METHODS
+
+The full documentation for the Watcher feature is available here:
+L<https://www.elastic.co/guide/en/x-pack/current/xpack-alerting.html>
+
+=head2 C<put_watch()>
+
+ $response = $es->watcher->put_watch(
+ id => $watch_id, # required
+ body => {...}
+ );
+
+The C<put_watch()> method is used to register a new watcher or to update
+an existing watcher.
+
+See the L<put_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-put-watch.html>
+for more information.
+
+Query string parameters:
+ C<active>,
+ C<error_trace>,
+ C<human>,
+ C<if_primary_term>,
+ C<if_seq_no>,
+ C<master_timeout>,
+ C<version>
+
+=head2 C<get_watch()>
+
+ $response = $es->watcher->get_watch(
+ id => $watch_id, # required
+ );
+
+The C<get_watch()> method is used to retrieve a watch by ID.
+
+See the L<get_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-get-watch.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_watch()>
+
+ $response = $es->watcher->delete_watch(
+ id => $watch_id, # required
+ );
+
+The C<delete_watch()> method is used to delete a watch by ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<force>,
+ C<human>,
+ C<master_timeout>
+
+See the L<delete_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-delete-watch.html>
+for more information.
+
+=head2 C<execute_watch()>
+
+ $response = $es->watcher->execute_watch(
+ id => $watch_id, # optional
+ body => {...} # optional
+ );
+
+The C<execute_watch()> method forces the execution of a previously
+registered watch. Optional parameters may be passed in the C<body>.
+
+Query string parameters:
+ C<debug>,
+ C<error_trace>,
+ C<human>
+
+See the L<execute_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-execute-watch.html>
+for more information.
+
+=head2 C<ack_watch()>
+
+ $response = $es->watcher->ack_watch(
+ watch_id => $watch_id, # required
+ action_id => $action_id | \@action_ids # optional
+ );
+
+The C<ack_watch()> method is used to manually throttle the execution of
+a watch.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
+
+See the L<ack_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-ack-watch.html>
+for more information.
+
+=head2 C<activate_watch()>
+
+ $response = $es->watcher->activate_watch(
+ watch_id => $watch_id, # required
+ );
+
+The C<activate_watch()> method is used to activate a deactive watch.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
+
+See the L<activate_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-activate-watch.html>
+for more information.
+
+=head2 C<deactivate_watch()>
+
+ $response = $es->watcher->deactivate_watch(
+ watch_id => $watch_id, # required
+ );
+
+The C<deactivate_watch()> method is used to deactivate an active watch.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
+
+See the L<deactivate_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-deactivate-watch.html>
+for more information.
+
+=head2 C<stats()>
+
+ $response = $es->watcher->stats(
+ metric => $metric # optional
+ );
+
+The C<stats()> method returns information about the status of the watcher plugin.
+
+See the L<stats docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-stats.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<stop()>
+
+ $response = $es->watcher->stop();
+
+The C<stop()> method stops the watcher service if it is running.
+
+See the L<stop docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-stop.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<start()>
+
+ $response = $es->watcher->start();
+
+The C<start()> method starts the watcher service if it is not already running.
+
+See the L<start docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-start.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<restart()>
+
+ $response = $es->watcher->restart();
+
+The C<restart()> method stops then starts the watcher service.
+
+See the L<restart docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-restart.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+
+
diff --git a/lib/Search/Elasticsearch/Client/7_0/Direct/XPack.pm b/lib/Search/Elasticsearch/Client/7_0/Direct/XPack.pm
new file mode 100644
index 0000000..83e74de
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Direct/XPack.pm
@@ -0,0 +1,60 @@
+# 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 Search::Elasticsearch::Client::7_0::Direct::XPack;
+
+use Moo;
+with 'Search::Elasticsearch::Client::7_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('xpack');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing XPack APIs for Search::Elasticsearch v7.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->xpack->info();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<xpack>
+namespace.
+
+=head1 METHODS
+
+=head2 C<info()>
+
+ my $response = $es->xpack->info();
+
+Provides general information about the installed X-Pack features.
+
+See the L<info|https://www.elastic.co/guide/en/elasticsearch/reference/current/info-api.html>
+for more information.
+
+=head2 C<usage()>
+
+ my $response = $es->xpack->usage();
+
+Provides usage information about the installed X-Pack features.
+
+See the L<usage|https://www.elastic.co/guide/en/elasticsearch/reference/current/usage-api.html>
+for more information.
\ No newline at end of file
diff --git a/lib/Search/Elasticsearch/Client/7_0/Role/API.pm b/lib/Search/Elasticsearch/Client/7_0/Role/API.pm
new file mode 100644
index 0000000..aafdbaf
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/Role/API.pm
@@ -0,0 +1,6379 @@
+# 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 Search::Elasticsearch::Client::7_0::Role::API;
+
+use Moo::Role;
+with 'Search::Elasticsearch::Role::API';
+
+use Search::Elasticsearch::Util qw(throw);
+use namespace::clean;
+
+has 'api_version' => ( is => 'ro', default => '7_0' );
+
+our %API;
+
+#===================================
+sub api {
+#===================================
+ my $name = $_[1] || return \%API;
+ return $API{$name}
+ || throw( 'Internal', "Unknown api name ($name)" );
+}
+
+#===================================
+%API = (
+#===================================
+
+ 'bulk.metadata' => {
+ params => {
+ '_index' => '_index',
+ 'index' => '_index',
+ '_id' => '_id',
+ 'id' => '_id',
+ 'pipeline' => 'pipeline',
+ 'routing' => 'routing',
+ '_routing' => 'routing',
+ 'parent' => 'parent',
+ '_parent' => 'parent',
+ 'timestamp' => 'timestamp',
+ '_timestamp' => 'timestamp',
+ 'ttl' => 'ttl',
+ '_ttl' => 'ttl',
+ 'version' => 'version',
+ '_version' => 'version',
+ 'version_type' => 'version_type',
+ '_version_type' => 'version_type',
+ 'if_seq_no' => 'if_seq_no',
+ 'if_primary_term' => 'if_primary_term',
+ 'lang' => 'lang',
+ 'require_alias' => 'require_alias',
+ 'refresh' => 'refresh',
+ 'retry_on_conflict' => 'retru_on_conflict',
+ 'wait_for_active_shards' => 'wait_for_active_shards',
+ '_source' => '_source',
+ '_source_excludes' => '_source_excludes',
+ '_source_includes' => '_source_includes',
+ 'timeout' => 'timeout'
+ }
+ },
+ 'bulk.update' => {
+ params => [
+ '_source', '_source_includes',
+ '_source_excludes', 'detect_noop',
+ 'doc', 'doc_as_upsert',
+ 'fields', 'retry_on_conflict',
+ 'scripted_upsert', 'script',
+ 'upsert', 'lang',
+ 'params'
+ ]
+ },
+ 'bulk.required' => { params => ['index'] },
+
+#=== AUTOGEN - START ===
+
+ 'bulk' => {
+ body => { required => 1 },
+ doc => "docs-bulk",
+ method => "POST",
+ parts => { index => {}, type => {} },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}", "{type}", "_bulk" ],
+ [ { index => 0 }, "{index}", "_bulk" ],
+ [ {}, "_bulk" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ pipeline => "string",
+ refresh => "enum",
+ require_alias => "boolean",
+ routing => "string",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ serialize => "bulk",
+ },
+
+ 'clear_scroll' => {
+ body => {},
+ doc => "clear-scroll-api",
+ method => "DELETE",
+ parts => { scroll_id => { multi => 1 } },
+ paths => [
+ [ { scroll_id => 2 }, "_search", "scroll", "{scroll_id}" ],
+ [ {}, "_search", "scroll" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'close_point_in_time' => {
+ body => {},
+ doc => "point-in-time-api",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_pit" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'count' => {
+ body => {},
+ doc => "search-count",
+ method => "POST",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}", "{type}", "_count" ],
+ [ { index => 0 }, "{index}", "_count" ],
+ [ {}, "_count" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ min_score => "number",
+ preference => "string",
+ q => "string",
+ routing => "list",
+ terminate_after => "number",
+ },
+ },
+
+ 'create' => {
+ body => { required => 1 },
+ doc => "docs-index_",
+ method => "PUT",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 },
+ "{index}", "{type}", "{id}", "_create",
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_create", "{id}" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ pipeline => "string",
+ refresh => "enum",
+ routing => "string",
+ timeout => "time",
+ version => "number",
+ version_type => "enum",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'delete' => {
+ doc => "docs-delete",
+ method => "DELETE",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 }, "{index}",
+ "{type}", "{id}"
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ refresh => "enum",
+ routing => "string",
+ timeout => "time",
+ version => "number",
+ version_type => "enum",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'delete_by_query' => {
+ body => { required => 1 },
+ doc => "docs-delete-by-query",
+ method => "POST",
+ parts => {
+ index => { multi => 1, required => 1 },
+ type => { multi => 1 }
+ },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}",
+ "{type}", "_delete_by_query",
+ ],
+ [ { index => 0 }, "{index}", "_delete_by_query" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ allow_no_indices => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ conflicts => "enum",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ max_docs => "number",
+ preference => "string",
+ q => "string",
+ refresh => "boolean",
+ request_cache => "boolean",
+ requests_per_second => "number",
+ routing => "list",
+ scroll => "time",
+ scroll_size => "number",
+ search_timeout => "time",
+ search_type => "enum",
+ size => "number",
+ slices => "number|string",
+ sort => "list",
+ stats => "list",
+ terminate_after => "number",
+ timeout => "time",
+ version => "boolean",
+ wait_for_active_shards => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'delete_by_query_rethrottle' => {
+ doc => "docs-delete-by-query",
+ method => "POST",
+ parts => { task_id => {} },
+ paths => [
+ [ { task_id => 1 }, "_delete_by_query",
+ "{task_id}", "_rethrottle",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ requests_per_second => "number",
+ },
+ },
+
+ 'delete_script' => {
+ doc => "modules-scripting",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 1 }, "_scripts", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'exists' => {
+ doc => "docs-get",
+ method => "HEAD",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 }, "{index}",
+ "{type}", "{id}"
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ stored_fields => "list",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'exists_source' => {
+ doc => "docs-get",
+ method => "HEAD",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 },
+ "{index}", "{type}", "{id}", "_source",
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_source", "{id}" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'explain' => {
+ body => {},
+ doc => "search-explain",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 }, "{index}",
+ "{type}", "{id}",
+ "_explain",
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_explain", "{id}" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ lenient => "boolean",
+ preference => "string",
+ q => "string",
+ routing => "string",
+ stored_fields => "list",
+ },
+ },
+
+ 'field_caps' => {
+ body => {},
+ doc => "search-field-caps",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_field_caps" ],
+ [ {}, "_field_caps" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ fields => "list",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_unmapped => "boolean",
+ },
+ },
+
+ 'get' => {
+ doc => "docs-get",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 }, "{index}",
+ "{type}", "{id}"
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ stored_fields => "list",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'get_script' => {
+ doc => "modules-scripting",
+ parts => { id => {} },
+ paths => [ [ { id => 1 }, "_scripts", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'get_script_context' => {
+ doc => "painless-contexts",
+ parts => {},
+ paths => [ [ {}, "_script_context" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'get_script_languages' => {
+ doc => "modules-scripting",
+ parts => {},
+ paths => [ [ {}, "_script_language" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'get_source' => {
+ doc => "docs-get",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 },
+ "{index}", "{type}", "{id}", "_source",
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_source", "{id}" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'index' => {
+ body => { required => 1 },
+ doc => "docs-index_",
+ method => "POST",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 }, "{index}",
+ "{type}", "{id}"
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ],
+ [ { index => 0, type => 1 }, "{index}", "{type}" ],
+ [ { index => 0 }, "{index}", "_doc" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ op_type => "enum",
+ pipeline => "string",
+ refresh => "enum",
+ require_alias => "boolean",
+ routing => "string",
+ timeout => "time",
+ version => "number",
+ version_type => "enum",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'info' => {
+ doc => "index",
+ parts => {},
+ paths => [ [ {} ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'mget' => {
+ body => { required => 1 },
+ doc => "docs-multi-get",
+ parts => { index => {}, type => {} },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}", "{type}", "_mget" ],
+ [ { index => 0 }, "{index}", "_mget" ],
+ [ {}, "_mget" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ stored_fields => "list",
+ },
+ },
+
+ 'msearch' => {
+ body => { required => 1 },
+ doc => "search-multi-search",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}", "{type}", "_msearch" ],
+ [ { index => 0 }, "{index}", "_msearch" ],
+ [ {}, "_msearch" ],
+ ],
+ qs => {
+ ccs_minimize_roundtrips => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ max_concurrent_searches => "number",
+ max_concurrent_shard_requests => "number",
+ pre_filter_shard_size => "number",
+ rest_total_hits_as_int => "boolean",
+ search_type => "enum",
+ typed_keys => "boolean",
+ },
+ serialize => "bulk",
+ },
+
+ 'msearch_template' => {
+ body => { required => 1 },
+ doc => "search-multi-search",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}",
+ "{type}", "_msearch",
+ "template",
+ ],
+ [ { index => 0 }, "{index}", "_msearch", "template" ],
+ [ {}, "_msearch", "template" ],
+ ],
+ qs => {
+ ccs_minimize_roundtrips => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ max_concurrent_searches => "number",
+ rest_total_hits_as_int => "boolean",
+ search_type => "enum",
+ typed_keys => "boolean",
+ },
+ serialize => "bulk",
+ },
+
+ 'mtermvectors' => {
+ body => {},
+ doc => "docs-multi-termvectors",
+ parts => { index => {}, type => {} },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}",
+ "{type}", "_mtermvectors"
+ ],
+ [ { index => 0 }, "{index}", "_mtermvectors" ],
+ [ {}, "_mtermvectors" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ field_statistics => "boolean",
+ fields => "list",
+ filter_path => "list",
+ human => "boolean",
+ ids => "list",
+ offsets => "boolean",
+ payloads => "boolean",
+ positions => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ routing => "string",
+ term_statistics => "boolean",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'open_point_in_time' => {
+ doc => "point-in-time-api",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_pit" ], [ {}, "_pit" ] ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ keep_alive => "string",
+ preference => "string",
+ routing => "string",
+ },
+ },
+
+ 'ping' => {
+ doc => "index",
+ method => "HEAD",
+ parts => {},
+ paths => [ [ {} ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'put_script' => {
+ body => { required => 1 },
+ doc => "modules-scripting",
+ method => "PUT",
+ parts => { context => {}, id => {} },
+ paths => [
+ [ { context => 2, id => 1 }, "_scripts", "{id}", "{context}" ],
+ [ { id => 1 }, "_scripts", "{id}" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'rank_eval' => {
+ body => { required => 1 },
+ doc => "search-rank-eval",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_rank_eval" ],
+ [ {}, "_rank_eval" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ search_type => "enum",
+ },
+ },
+
+ 'reindex' => {
+ body => { required => 1 },
+ doc => "docs-reindex",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_reindex" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ max_docs => "number",
+ refresh => "boolean",
+ requests_per_second => "number",
+ scroll => "time",
+ slices => "number|string",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'reindex_rethrottle' => {
+ doc => "docs-reindex",
+ method => "POST",
+ parts => { task_id => {} },
+ paths =>
+ [ [ { task_id => 1 }, "_reindex", "{task_id}", "_rethrottle" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ requests_per_second => "number",
+ },
+ },
+
+ 'render_search_template' => {
+ body => {},
+ doc => "",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_render", "template", "{id}" ],
+ [ {}, "_render", "template" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'scripts_painless_execute' => {
+ body => {},
+ doc => "painless-execute-api",
+ parts => {},
+ paths => [ [ {}, "_scripts", "painless", "_execute" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'scroll' => {
+ body => {},
+ doc => "",
+ parts => { scroll_id => {} },
+ paths => [
+ [ { scroll_id => 2 }, "_search", "scroll", "{scroll_id}" ],
+ [ {}, "_search", "scroll" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ rest_total_hits_as_int => "boolean",
+ scroll => "time",
+ },
+ },
+
+ 'search' => {
+ body => {},
+ doc => "search-search",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}", "{type}", "_search" ],
+ [ { index => 0 }, "{index}", "_search" ],
+ [ {}, "_search" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ allow_no_indices => "boolean",
+ allow_partial_search_results => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ batched_reduce_size => "number",
+ ccs_minimize_roundtrips => "boolean",
+ default_operator => "enum",
+ df => "string",
+ docvalue_fields => "list",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ explain => "boolean",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ max_concurrent_shard_requests => "number",
+ pre_filter_shard_size => "number",
+ preference => "string",
+ q => "string",
+ request_cache => "boolean",
+ rest_total_hits_as_int => "boolean",
+ routing => "list",
+ scroll => "time",
+ search_type => "enum",
+ seq_no_primary_term => "boolean",
+ size => "number",
+ sort => "list",
+ stats => "list",
+ stored_fields => "list",
+ suggest_field => "string",
+ suggest_mode => "enum",
+ suggest_size => "number",
+ suggest_text => "string",
+ terminate_after => "number",
+ timeout => "time",
+ track_scores => "boolean",
+ track_total_hits => "boolean",
+ typed_keys => "boolean",
+ version => "boolean",
+ },
+ },
+
+ 'search_shards' => {
+ doc => "search-shards",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_search_shards" ],
+ [ {}, "_search_shards" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ preference => "string",
+ routing => "string",
+ },
+ },
+
+ 'search_template' => {
+ body => { required => 1 },
+ doc => "search-template",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}",
+ "{type}", "_search",
+ "template",
+ ],
+ [ { index => 0 }, "{index}", "_search", "template" ],
+ [ {}, "_search", "template" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ ccs_minimize_roundtrips => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ explain => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ preference => "string",
+ profile => "boolean",
+ rest_total_hits_as_int => "boolean",
+ routing => "list",
+ scroll => "time",
+ search_type => "enum",
+ typed_keys => "boolean",
+ },
+ },
+
+ 'termvectors' => {
+ body => {},
+ doc => "docs-termvectors",
+ parts => { id => {}, index => { required => 1 }, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 }, "{index}",
+ "{type}", "{id}",
+ "_termvectors",
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_termvectors", "{id}" ],
+ [ { index => 0, type => 1 }, "{index}", "{type}",
+ "_termvectors"
+ ],
+ [ { index => 0 }, "{index}", "_termvectors" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ field_statistics => "boolean",
+ fields => "list",
+ filter_path => "list",
+ human => "boolean",
+ offsets => "boolean",
+ payloads => "boolean",
+ positions => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ routing => "string",
+ term_statistics => "boolean",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'update' => {
+ body => { required => 1 },
+ doc => "docs-update",
+ method => "POST",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 },
+ "{index}", "{type}", "{id}", "_update",
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_update", "{id}" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ lang => "string",
+ refresh => "enum",
+ require_alias => "boolean",
+ retry_on_conflict => "number",
+ routing => "string",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'update_by_query' => {
+ body => {},
+ doc => "docs-update-by-query",
+ method => "POST",
+ parts => {
+ index => { multi => 1, required => 1 },
+ type => { multi => 1 }
+ },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}",
+ "{type}", "_update_by_query",
+ ],
+ [ { index => 0 }, "{index}", "_update_by_query" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ allow_no_indices => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ conflicts => "enum",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ max_docs => "number",
+ pipeline => "string",
+ preference => "string",
+ q => "string",
+ refresh => "boolean",
+ request_cache => "boolean",
+ requests_per_second => "number",
+ routing => "list",
+ scroll => "time",
+ scroll_size => "number",
+ search_timeout => "time",
+ search_type => "enum",
+ size => "number",
+ slices => "number|string",
+ sort => "list",
+ stats => "list",
+ terminate_after => "number",
+ timeout => "time",
+ version => "boolean",
+ version_type => "boolean",
+ wait_for_active_shards => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'update_by_query_rethrottle' => {
+ doc => "docs-update-by-query",
+ method => "POST",
+ parts => { task_id => {} },
+ paths => [
+ [ { task_id => 1 }, "_update_by_query",
+ "{task_id}", "_rethrottle",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ requests_per_second => "number",
+ },
+ },
+
+ 'async_search.delete' => {
+ doc => "async-search",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 1 }, "_async_search", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'async_search.get' => {
+ doc => "async-search",
+ parts => { id => {} },
+ paths => [ [ { id => 1 }, "_async_search", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ keep_alive => "time",
+ typed_keys => "boolean",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'async_search.status' => {
+ doc => "async-search",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_async_search", "status", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'async_search.submit' => {
+ body => {},
+ doc => "async-search",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_async_search" ],
+ [ {}, "_async_search" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ allow_no_indices => "boolean",
+ allow_partial_search_results => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ batched_reduce_size => "number",
+ default_operator => "enum",
+ df => "string",
+ docvalue_fields => "list",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ explain => "boolean",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ keep_alive => "time",
+ keep_on_completion => "boolean",
+ lenient => "boolean",
+ max_concurrent_shard_requests => "number",
+ preference => "string",
+ q => "string",
+ request_cache => "boolean",
+ routing => "list",
+ search_type => "enum",
+ seq_no_primary_term => "boolean",
+ size => "number",
+ sort => "list",
+ stats => "list",
+ stored_fields => "list",
+ suggest_field => "string",
+ suggest_mode => "enum",
+ suggest_size => "number",
+ suggest_text => "string",
+ terminate_after => "number",
+ timeout => "time",
+ track_scores => "boolean",
+ track_total_hits => "boolean",
+ typed_keys => "boolean",
+ version => "boolean",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'autoscaling.delete_autoscaling_policy' => {
+ doc => "autoscaling-delete-autoscaling-policy",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_autoscaling", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'autoscaling.get_autoscaling_capacity' => {
+ doc => "autoscaling-get-autoscaling-capacity",
+ parts => {},
+ paths => [ [ {}, "_autoscaling", "capacity" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'autoscaling.get_autoscaling_policy' => {
+ doc => "autoscaling-get-autoscaling-policy",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_autoscaling", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'autoscaling.put_autoscaling_policy' => {
+ body => { required => 1 },
+ doc => "autoscaling-put-autoscaling-policy",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_autoscaling", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'cat.aliases' => {
+ doc => "cat-alias",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_cat", "aliases", "{name}" ],
+ [ {}, "_cat", "aliases" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.allocation' => {
+ doc => "cat-allocation",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 2 }, "_cat", "allocation", "{node_id}" ],
+ [ {}, "_cat", "allocation" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.count' => {
+ doc => "cat-count",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "count", "{index}" ],
+ [ {}, "_cat", "count" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.fielddata' => {
+ doc => "cat-fielddata",
+ parts => { fields => { multi => 1 } },
+ paths => [
+ [ { fields => 2 }, "_cat", "fielddata", "{fields}" ],
+ [ {}, "_cat", "fielddata" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.health' => {
+ doc => "cat-health",
+ parts => {},
+ paths => [ [ {}, "_cat", "health" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ ts => "boolean",
+ v => "boolean",
+ },
+ },
+
+ 'cat.help' => {
+ doc => "cat",
+ parts => {},
+ paths => [ [ {}, "_cat" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ },
+ },
+
+ 'cat.indices' => {
+ doc => "cat-indices",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "indices", "{index}" ],
+ [ {}, "_cat", "indices" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ health => "enum",
+ help => "boolean",
+ human => "boolean",
+ include_unloaded_segments => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ pri => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.master' => {
+ doc => "cat-master",
+ parts => {},
+ paths => [ [ {}, "_cat", "master" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.ml_data_frame_analytics' => {
+ doc => "cat-dfanalytics",
+ parts => { id => {} },
+ paths => [
+ [ { id => 4 }, "_cat", "ml", "data_frame", "analytics", "{id}" ],
+ [ {}, "_cat", "ml", "data_frame", "analytics" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.ml_datafeeds' => {
+ doc => "cat-datafeeds",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 3 }, "_cat",
+ "ml", "datafeeds",
+ "{datafeed_id}"
+ ],
+ [ {}, "_cat", "ml", "datafeeds" ],
+ ],
+ qs => {
+ allow_no_datafeeds => "boolean",
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.ml_jobs' => {
+ doc => "cat-anomaly-detectors",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 3 }, "_cat", "ml", "anomaly_detectors",
+ "{job_id}"
+ ],
+ [ {}, "_cat", "ml", "anomaly_detectors" ],
+ ],
+ qs => {
+ allow_no_jobs => "boolean",
+ allow_no_match => "boolean",
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.ml_trained_models' => {
+ doc => "cat-trained-model",
+ parts => { model_id => {} },
+ paths => [
+ [ { model_id => 3 }, "_cat",
+ "ml", "trained_models",
+ "{model_id}"
+ ],
+ [ {}, "_cat", "ml", "trained_models" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ from => "int",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ size => "int",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.nodeattrs' => {
+ doc => "cat-nodeattrs",
+ parts => {},
+ paths => [ [ {}, "_cat", "nodeattrs" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.nodes' => {
+ doc => "cat-nodes",
+ parts => {},
+ paths => [ [ {}, "_cat", "nodes" ] ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ full_id => "boolean",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.pending_tasks' => {
+ doc => "cat-pending-tasks",
+ parts => {},
+ paths => [ [ {}, "_cat", "pending_tasks" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.plugins' => {
+ doc => "cat-plugins",
+ parts => {},
+ paths => [ [ {}, "_cat", "plugins" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.recovery' => {
+ doc => "cat-recovery",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "recovery", "{index}" ],
+ [ {}, "_cat", "recovery" ],
+ ],
+ qs => {
+ active_only => "boolean",
+ bytes => "enum",
+ detailed => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.repositories' => {
+ doc => "cat-repositories",
+ parts => {},
+ paths => [ [ {}, "_cat", "repositories" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.segments' => {
+ doc => "cat-segments",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "segments", "{index}" ],
+ [ {}, "_cat", "segments" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.shards' => {
+ doc => "cat-shards",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "shards", "{index}" ],
+ [ {}, "_cat", "shards" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.snapshots' => {
+ doc => "cat-snapshots",
+ parts => { repository => { multi => 1 } },
+ paths => [
+ [ { repository => 2 }, "_cat", "snapshots", "{repository}" ],
+ [ {}, "_cat", "snapshots" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.tasks' => {
+ doc => "tasks",
+ parts => {},
+ paths => [ [ {}, "_cat", "tasks" ] ],
+ qs => {
+ actions => "list",
+ detailed => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ nodes => "list",
+ parent_task_id => "string",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.templates' => {
+ doc => "cat-templates",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_cat", "templates", "{name}" ],
+ [ {}, "_cat", "templates" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.thread_pool' => {
+ doc => "cat-thread-pool",
+ parts => { thread_pool_patterns => { multi => 1 } },
+ paths => [
+ [ { thread_pool_patterns => 2 }, "_cat",
+ "thread_pool", "{thread_pool_patterns}",
+ ],
+ [ {}, "_cat", "thread_pool" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ size => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.transforms' => {
+ doc => "cat-transforms",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 2 }, "_cat", "transforms", "{transform_id}" ],
+ [ {}, "_cat", "transforms" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ from => "int",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ size => "int",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'ccr.delete_auto_follow_pattern' => {
+ doc => "ccr-delete-auto-follow-pattern",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_ccr", "auto_follow", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.follow' => {
+ body => { required => 1 },
+ doc => "ccr-put-follow",
+ method => "PUT",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "follow" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'ccr.follow_info' => {
+ doc => "ccr-get-follow-info",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "info" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.follow_stats' => {
+ doc => "ccr-get-follow-stats",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.forget_follower' => {
+ body => { required => 1 },
+ doc => "ccr-post-forget-follower",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "forget_follower" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.get_auto_follow_pattern' => {
+ doc => "ccr-get-auto-follow-pattern",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_ccr", "auto_follow", "{name}" ],
+ [ {}, "_ccr", "auto_follow" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.pause_auto_follow_pattern' => {
+ doc => "ccr-pause-auto-follow-pattern",
+ method => "POST",
+ parts => { name => {} },
+ paths =>
+ [ [ { name => 2 }, "_ccr", "auto_follow", "{name}", "pause" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.pause_follow' => {
+ doc => "ccr-post-pause-follow",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "pause_follow" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.put_auto_follow_pattern' => {
+ body => { required => 1 },
+ doc => "ccr-put-auto-follow-pattern",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_ccr", "auto_follow", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.resume_auto_follow_pattern' => {
+ doc => "ccr-resume-auto-follow-pattern",
+ method => "POST",
+ parts => { name => {} },
+ paths =>
+ [ [ { name => 2 }, "_ccr", "auto_follow", "{name}", "resume" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.resume_follow' => {
+ body => {},
+ doc => "ccr-post-resume-follow",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "resume_follow" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.stats' => {
+ doc => "ccr-get-stats",
+ parts => {},
+ paths => [ [ {}, "_ccr", "stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.unfollow' => {
+ doc => "ccr-post-unfollow",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "unfollow" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'cluster.allocation_explain' => {
+ body => {},
+ doc => "cluster-allocation-explain",
+ parts => {},
+ paths => [ [ {}, "_cluster", "allocation", "explain" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ include_disk_info => "boolean",
+ include_yes_decisions => "boolean",
+ },
+ },
+
+ 'cluster.delete_component_template' => {
+ doc => "indices-component-template",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_component_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.delete_voting_config_exclusions' => {
+ doc => "voting-config-exclusions",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_cluster", "voting_config_exclusions" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ wait_for_removal => "boolean",
+ },
+ },
+
+ 'cluster.exists_component_template' => {
+ doc => "indices-component-template",
+ method => "HEAD",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_component_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'cluster.get_component_template' => {
+ doc => "indices-component-template",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 1 }, "_component_template", "{name}" ],
+ [ {}, "_component_template" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'cluster.get_settings' => {
+ doc => "cluster-update-settings",
+ parts => {},
+ paths => [ [ {}, "_cluster", "settings" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ include_defaults => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.health' => {
+ doc => "cluster-health",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cluster", "health", "{index}" ],
+ [ {}, "_cluster", "health" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ level => "enum",
+ local => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ wait_for_events => "enum",
+ wait_for_no_initializing_shards => "boolean",
+ wait_for_no_relocating_shards => "boolean",
+ wait_for_nodes => "string",
+ wait_for_status => "enum",
+ },
+ },
+
+ 'cluster.pending_tasks' => {
+ doc => "cluster-pending",
+ parts => {},
+ paths => [ [ {}, "_cluster", "pending_tasks" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'cluster.post_voting_config_exclusions' => {
+ doc => "voting-config-exclusions",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_cluster", "voting_config_exclusions" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ node_ids => "string",
+ node_names => "string",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.put_component_template' => {
+ body => { required => 1 },
+ doc => "indices-component-template",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_component_template", "{name}" ] ],
+ qs => {
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.put_settings' => {
+ body => { required => 1 },
+ doc => "cluster-update-settings",
+ method => "PUT",
+ parts => {},
+ paths => [ [ {}, "_cluster", "settings" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.remote_info' => {
+ doc => "cluster-remote-info",
+ parts => {},
+ paths => [ [ {}, "_remote", "info" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'cluster.reroute' => {
+ body => {},
+ doc => "cluster-reroute",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_cluster", "reroute" ] ],
+ qs => {
+ dry_run => "boolean",
+ error_trace => "boolean",
+ explain => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ metric => "list",
+ retry_failed => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.state' => {
+ doc => "cluster-state",
+ parts => { index => { multi => 1 }, metric => { multi => 1 } },
+ paths => [
+ [ { index => 3, metric => 2 }, "_cluster",
+ "state", "{metric}",
+ "{index}",
+ ],
+ [ { metric => 2 }, "_cluster", "state", "{metric}" ],
+ [ {}, "_cluster", "state" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ wait_for_metadata_version => "number",
+ wait_for_timeout => "time",
+ },
+ },
+
+ 'cluster.stats' => {
+ doc => "cluster-stats",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 3 }, "_cluster", "stats", "nodes", "{node_id}" ],
+ [ {}, "_cluster", "stats" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'dangling_indices.delete_dangling_index' => {
+ doc => "modules-gateway-dangling-indices",
+ method => "DELETE",
+ parts => { index_uuid => {} },
+ paths => [ [ { index_uuid => 1 }, "_dangling", "{index_uuid}" ] ],
+ qs => {
+ accept_data_loss => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'dangling_indices.import_dangling_index' => {
+ doc => "modules-gateway-dangling-indices",
+ method => "POST",
+ parts => { index_uuid => {} },
+ paths => [ [ { index_uuid => 1 }, "_dangling", "{index_uuid}" ] ],
+ qs => {
+ accept_data_loss => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'dangling_indices.list_dangling_indices' => {
+ doc => "modules-gateway-dangling-indices",
+ parts => {},
+ paths => [ [ {}, "_dangling" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'data_frame_transform_deprecated.delete_transform' => {
+ doc => "delete-transform",
+ method => "DELETE",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 2 }, "_data_frame",
+ "transforms", "{transform_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ },
+ },
+
+ 'data_frame_transform_deprecated.get_transform' => {
+ doc => "get-transform",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 2 }, "_data_frame",
+ "transforms", "{transform_id}",
+ ],
+ [ {}, "_data_frame", "transforms" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'data_frame_transform_deprecated.get_transform_stats' => {
+ doc => "get-transform-stats",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 2 }, "_data_frame",
+ "transforms", "{transform_id}",
+ "_stats",
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ size => "number",
+ },
+ },
+
+ 'data_frame_transform_deprecated.preview_transform' => {
+ body => { required => 1 },
+ doc => "preview-transform",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_data_frame", "transforms", "_preview" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'data_frame_transform_deprecated.put_transform' => {
+ body => { required => 1 },
+ doc => "put-transform",
+ method => "PUT",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 2 }, "_data_frame",
+ "transforms", "{transform_id}",
+ ],
+ ],
+ qs => {
+ defer_validation => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'data_frame_transform_deprecated.start_transform' => {
+ doc => "start-transform",
+ method => "POST",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 2 }, "_data_frame",
+ "transforms", "{transform_id}",
+ "_start",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'data_frame_transform_deprecated.stop_transform' => {
+ doc => "stop-transform",
+ method => "POST",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 2 }, "_data_frame",
+ "transforms", "{transform_id}",
+ "_stop",
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'data_frame_transform_deprecated.update_transform' => {
+ body => { required => 1 },
+ doc => "update-transform",
+ method => "POST",
+ parts => { transform_id => { required => 1 } },
+ paths => [
+ [ { transform_id => 2 }, "_data_frame",
+ "transforms", "{transform_id}",
+ "_update",
+ ],
+ ],
+ qs => {
+ defer_validation => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'enrich.delete_policy' => {
+ doc => "delete-enrich-policy-api",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_enrich", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'enrich.execute_policy' => {
+ doc => "execute-enrich-policy-api",
+ method => "PUT",
+ parts => { name => {} },
+ paths =>
+ [ [ { name => 2 }, "_enrich", "policy", "{name}", "_execute" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'enrich.get_policy' => {
+ doc => "get-enrich-policy-api",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_enrich", "policy", "{name}" ],
+ [ {}, "_enrich", "policy" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'enrich.put_policy' => {
+ body => { required => 1 },
+ doc => "put-enrich-policy-api",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_enrich", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'enrich.stats' => {
+ doc => "enrich-stats-api",
+ parts => {},
+ paths => [ [ {}, "_enrich", "_stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'eql.delete' => {
+ doc => "eql-search-api",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_eql", "search", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'eql.get' => {
+ doc => "eql-search-api",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_eql", "search", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ keep_alive => "time",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'eql.search' => {
+ body => { required => 1 },
+ doc => "eql-search-api",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_eql", "search" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ keep_alive => "time",
+ keep_on_completion => "boolean",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'graph.explore' => {
+ body => {},
+ doc => "graph-explore-api",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}",
+ "{type}", "_graph",
+ "explore",
+ ],
+ [ { index => 0 }, "{index}", "_graph", "explore" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ routing => "string",
+ timeout => "time",
+ },
+ },
+
+ 'ilm.delete_lifecycle' => {
+ doc => "ilm-delete-lifecycle",
+ method => "DELETE",
+ parts => { policy => {} },
+ paths => [ [ { policy => 2 }, "_ilm", "policy", "{policy}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.explain_lifecycle' => {
+ doc => "ilm-explain-lifecycle",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ilm", "explain" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ only_errors => "boolean",
+ only_managed => "boolean",
+ },
+ },
+
+ 'ilm.get_lifecycle' => {
+ doc => "ilm-get-lifecycle",
+ parts => { policy => {} },
+ paths => [
+ [ { policy => 2 }, "_ilm", "policy", "{policy}" ],
+ [ {}, "_ilm", "policy" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.get_status' => {
+ doc => "ilm-get-status",
+ parts => {},
+ paths => [ [ {}, "_ilm", "status" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.move_to_step' => {
+ body => {},
+ doc => "ilm-move-to-step",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 2 }, "_ilm", "move", "{index}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.put_lifecycle' => {
+ body => {},
+ doc => "ilm-put-lifecycle",
+ method => "PUT",
+ parts => { policy => {} },
+ paths => [ [ { policy => 2 }, "_ilm", "policy", "{policy}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.remove_policy' => {
+ doc => "ilm-remove-policy",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ilm", "remove" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.retry' => {
+ doc => "ilm-retry-policy",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ilm", "retry" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.start' => {
+ doc => "ilm-start",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ilm", "start" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.stop' => {
+ doc => "ilm-stop",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ilm", "stop" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.add_block' => {
+ doc => "index-modules-blocks",
+ method => "PUT",
+ parts => { block => {}, index => { multi => 1 } },
+ paths => [
+ [ { block => 2, index => 0 }, "{index}", "_block", "{block}" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.analyze' => {
+ body => {},
+ doc => "indices-analyze",
+ parts => { index => {} },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_analyze" ], [ {}, "_analyze" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.clear_cache' => {
+ doc => "indices-clearcache",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_cache", "clear" ],
+ [ {}, "_cache", "clear" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ fielddata => "boolean",
+ fields => "list",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ query => "boolean",
+ request => "boolean",
+ },
+ },
+
+ 'indices.clone' => {
+ body => {},
+ doc => "indices-clone-index",
+ method => "PUT",
+ parts => { index => {}, target => {} },
+ paths => [
+ [ { index => 0, target => 2 }, "{index}", "_clone", "{target}" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.close' => {
+ doc => "indices-open-close",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_close" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.create' => {
+ body => {},
+ doc => "indices-create-index",
+ method => "PUT",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ include_type_name => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.create_data_stream' => {
+ doc => "data-streams",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_data_stream", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.data_streams_stats' => {
+ doc => "data-streams",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 1 }, "_data_stream", "{name}", "_stats" ],
+ [ {}, "_data_stream", "_stats" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.delete' => {
+ doc => "indices-delete-index",
+ method => "DELETE",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.delete_alias' => {
+ doc => "indices-aliases",
+ method => "DELETE",
+ parts => { index => { multi => 1 }, name => { multi => 1 } },
+ paths =>
+ [ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.delete_data_stream' => {
+ doc => "data-streams",
+ method => "DELETE",
+ parts => { name => { multi => 1 } },
+ paths => [ [ { name => 1 }, "_data_stream", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'indices.delete_index_template' => {
+ doc => "indices-templates",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_index_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.delete_template' => {
+ doc => "indices-templates",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.exists' => {
+ doc => "indices-exists",
+ method => "HEAD",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_defaults => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.exists_alias' => {
+ doc => "indices-aliases",
+ method => "HEAD",
+ parts => { index => { multi => 1 }, name => { multi => 1 } },
+ paths => [
+ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ],
+ [ { name => 1 }, "_alias", "{name}" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.exists_index_template' => {
+ doc => "indices-templates",
+ method => "HEAD",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_index_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.exists_template' => {
+ doc => "indices-templates",
+ method => "HEAD",
+ parts => { name => { multi => 1 } },
+ paths => [ [ { name => 1 }, "_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.exists_type' => {
+ doc => "indices-types-exists",
+ method => "HEAD",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 2 }, "{index}", "_mapping", "{type}" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.flush' => {
+ doc => "indices-flush",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_flush" ], [ {}, "_flush" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ wait_if_ongoing => "boolean",
+ },
+ },
+
+ 'indices.flush_synced' => {
+ doc => "indices-synced-flush-api",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_flush", "synced" ],
+ [ {}, "_flush", "synced" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'indices.forcemerge' => {
+ doc => "indices-forcemerge",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_forcemerge" ],
+ [ {}, "_forcemerge" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flush => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ max_num_segments => "number",
+ only_expunge_deletes => "boolean",
+ },
+ },
+
+ 'indices.freeze' => {
+ doc => "freeze-index-api",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_freeze" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.get' => {
+ doc => "indices-get-index",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_defaults => "boolean",
+ include_type_name => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_alias' => {
+ doc => "indices-aliases",
+ parts => { index => { multi => 1 }, name => { multi => 1 } },
+ paths => [
+ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ],
+ [ { index => 0 }, "{index}", "_alias" ],
+ [ { name => 1 }, "_alias", "{name}" ],
+ [ {}, "_alias" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.get_data_stream' => {
+ doc => "data-streams",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 1 }, "_data_stream", "{name}" ],
+ [ {}, "_data_stream" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'indices.get_field_mapping' => {
+ doc => "indices-get-field-mapping",
+ parts => {
+ fields => { multi => 1 },
+ index => { multi => 1 },
+ type => { multi => 1 },
+ },
+ paths => [
+ [ { fields => 4, index => 0, type => 2 }, "{index}",
+ "_mapping", "{type}",
+ "field", "{fields}",
+ ],
+ [ { fields => 3, index => 0 }, "{index}",
+ "_mapping", "field",
+ "{fields}",
+ ],
+ [ { fields => 3, type => 1 }, "_mapping",
+ "{type}", "field",
+ "{fields}",
+ ],
+ [ { fields => 2 }, "_mapping", "field", "{fields}" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_defaults => "boolean",
+ include_type_name => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.get_index_template' => {
+ doc => "indices-templates",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 1 }, "_index_template", "{name}" ],
+ [ {}, "_index_template" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_mapping' => {
+ doc => "indices-get-mapping",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 2 }, "{index}", "_mapping", "{type}" ],
+ [ { index => 0 }, "{index}", "_mapping" ],
+ [ { type => 1 }, "_mapping", "{type}" ],
+ [ {}, "_mapping" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_type_name => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_settings' => {
+ doc => "indices-get-settings",
+ parts => { index => { multi => 1 }, name => { multi => 1 } },
+ paths => [
+ [ { index => 0, name => 2 }, "{index}", "_settings", "{name}" ],
+ [ { index => 0 }, "{index}", "_settings" ],
+ [ { name => 1 }, "_settings", "{name}" ],
+ [ {}, "_settings" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_defaults => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_template' => {
+ doc => "indices-templates",
+ parts => { name => { multi => 1 } },
+ paths =>
+ [ [ { name => 1 }, "_template", "{name}" ], [ {}, "_template" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ include_type_name => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_upgrade' => {
+ doc => "indices-upgrade",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_upgrade" ], [ {}, "_upgrade" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'indices.migrate_to_data_stream' => {
+ doc => "data-streams",
+ method => "POST",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_data_stream", "_migrate", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.open' => {
+ doc => "indices-open-close",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_open" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.promote_data_stream' => {
+ doc => "data-streams",
+ method => "POST",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_data_stream", "_promote", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.put_alias' => {
+ body => {},
+ doc => "indices-aliases",
+ method => "PUT",
+ parts => { index => { multi => 1 }, name => {} },
+ paths =>
+ [ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.put_index_template' => {
+ body => { required => 1 },
+ doc => "indices-templates",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_index_template", "{name}" ] ],
+ qs => {
+ cause => "string",
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.put_mapping' => {
+ body => { required => 1 },
+ doc => "indices-put-mapping",
+ method => "PUT",
+ parts => { index => { multi => 1 }, type => {} },
+ paths => [
+ [ { index => 0, type => 2 }, "{index}", "_mapping", "{type}" ],
+ [ { index => 0 }, "{index}", "_mapping" ],
+ [ { type => 1 }, "_mapping", "{type}" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_type_name => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ write_index_only => "boolean",
+ },
+ },
+
+ 'indices.put_settings' => {
+ body => { required => 1 },
+ doc => "indices-update-settings",
+ method => "PUT",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_settings" ],
+ [ {}, "_settings" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ preserve_existing => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'indices.put_template' => {
+ body => { required => 1 },
+ doc => "indices-templates",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_template", "{name}" ] ],
+ qs => {
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ include_type_name => "boolean",
+ master_timeout => "time",
+ order => "number",
+ },
+ },
+
+ 'indices.recovery' => {
+ doc => "indices-recovery",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_recovery" ],
+ [ {}, "_recovery" ]
+ ],
+ qs => {
+ active_only => "boolean",
+ detailed => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'indices.refresh' => {
+ doc => "indices-refresh",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_refresh" ], [ {}, "_refresh" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'indices.reload_search_analyzers' => {
+ doc => "indices-reload-analyzers",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_reload_search_analyzers" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'indices.resolve_index' => {
+ doc => "indices-resolve-index-api",
+ parts => { name => { multi => 1 } },
+ paths => [ [ { name => 2 }, "_resolve", "index", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'indices.rollover' => {
+ body => {},
+ doc => "indices-rollover-index",
+ method => "POST",
+ parts => { alias => {}, new_index => {} },
+ paths => [
+ [ { alias => 0, new_index => 2 }, "{alias}",
+ "_rollover", "{new_index}",
+ ],
+ [ { alias => 0 }, "{alias}", "_rollover" ],
+ ],
+ qs => {
+ dry_run => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ include_type_name => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.segments' => {
+ doc => "indices-segments",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_segments" ],
+ [ {}, "_segments" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ verbose => "boolean",
+ },
+ },
+
+ 'indices.shard_stores' => {
+ doc => "indices-shards-stores",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_shard_stores" ],
+ [ {}, "_shard_stores" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ status => "list",
+ },
+ },
+
+ 'indices.shrink' => {
+ body => {},
+ doc => "indices-shrink-index",
+ method => "PUT",
+ parts => { index => {}, target => {} },
+ paths => [
+ [ { index => 0, target => 2 }, "{index}", "_shrink", "{target}" ],
+ ],
+ qs => {
+ copy_settings => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.simulate_index_template' => {
+ body => {},
+ doc => "indices-templates",
+ method => "POST",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_index_template", "_simulate_index", "{name}" ],
+ ],
+ qs => {
+ cause => "string",
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.simulate_template' => {
+ body => {},
+ doc => "indices-templates",
+ method => "POST",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_index_template", "_simulate", "{name}" ],
+ [ {}, "_index_template", "_simulate" ],
+ ],
+ qs => {
+ cause => "string",
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.split' => {
+ body => {},
+ doc => "indices-split-index",
+ method => "PUT",
+ parts => { index => {}, target => {} },
+ paths => [
+ [ { index => 0, target => 2 }, "{index}", "_split", "{target}" ],
+ ],
+ qs => {
+ copy_settings => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.stats' => {
+ doc => "indices-stats",
+ parts => { index => { multi => 1 }, metric => { multi => 1 } },
+ paths => [
+ [ { index => 0, metric => 2 }, "{index}", "_stats", "{metric}" ],
+ [ { index => 0 }, "{index}", "_stats" ],
+ [ { metric => 1 }, "_stats", "{metric}" ],
+ [ {}, "_stats" ],
+ ],
+ qs => {
+ completion_fields => "list",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ fielddata_fields => "list",
+ fields => "list",
+ filter_path => "list",
+ forbid_closed_indices => "boolean",
+ groups => "list",
+ human => "boolean",
+ include_segment_file_sizes => "boolean",
+ include_unloaded_segments => "boolean",
+ level => "enum",
+ types => "list",
+ },
+ },
+
+ 'indices.unfreeze' => {
+ doc => "unfreeze-index-api",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_unfreeze" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.update_aliases' => {
+ body => { required => 1 },
+ doc => "indices-aliases",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_aliases" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.upgrade' => {
+ doc => "indices-upgrade",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_upgrade" ], [ {}, "_upgrade" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ only_ancient_segments => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'indices.validate_query' => {
+ body => {},
+ doc => "search-validate",
+ parts => { index => { multi => 1 }, type => { multi => 1 } },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}",
+ "{type}", "_validate",
+ "query",
+ ],
+ [ { index => 0 }, "{index}", "_validate", "query" ],
+ [ {}, "_validate", "query" ],
+ ],
+ qs => {
+ all_shards => "boolean",
+ allow_no_indices => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ explain => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ q => "string",
+ rewrite => "boolean",
+ },
+ },
+
+ 'ingest.delete_pipeline' => {
+ doc => "delete-pipeline-api",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_ingest", "pipeline", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'ingest.get_pipeline' => {
+ doc => "get-pipeline-api",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_ingest", "pipeline", "{id}" ],
+ [ {}, "_ingest", "pipeline" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'ingest.processor_grok' => {
+ doc => "",
+ parts => {},
+ paths => [ [ {}, "_ingest", "processor", "grok" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ingest.put_pipeline' => {
+ body => { required => 1 },
+ doc => "put-pipeline-api",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_ingest", "pipeline", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'ingest.simulate' => {
+ body => { required => 1 },
+ doc => "simulate-pipeline-api",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_ingest", "pipeline", "{id}", "_simulate" ],
+ [ {}, "_ingest", "pipeline", "_simulate" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ verbose => "boolean",
+ },
+ },
+
+ 'license.delete' => {
+ doc => "delete-license",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_license" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'license.get' => {
+ doc => "get-license",
+ parts => {},
+ paths => [ [ {}, "_license" ] ],
+ qs => {
+ accept_enterprise => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'license.get_basic_status' => {
+ doc => "get-basic-status",
+ parts => {},
+ paths => [ [ {}, "_license", "basic_status" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'license.get_trial_status' => {
+ doc => "get-trial-status",
+ parts => {},
+ paths => [ [ {}, "_license", "trial_status" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'license.post' => {
+ body => {},
+ doc => "update-license",
+ method => "PUT",
+ parts => {},
+ paths => [ [ {}, "_license" ] ],
+ qs => {
+ acknowledge => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'license.post_start_basic' => {
+ doc => "start-basic",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_license", "start_basic" ] ],
+ qs => {
+ acknowledge => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'license.post_start_trial' => {
+ doc => "start-trial",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_license", "start_trial" ] ],
+ qs => {
+ acknowledge => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ type => "string",
+ },
+ },
+
+ 'migration.deprecations' => {
+ doc => "migration-api-deprecation",
+ parts => { index => {} },
+ paths => [
+ [ { index => 0 }, "{index}", "_migration", "deprecations" ],
+ [ {}, "_migration", "deprecations" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.close_job' => {
+ body => {},
+ doc => "ml-close-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_close",
+ ],
+ ],
+ qs => {
+ allow_no_jobs => "boolean",
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_calendar' => {
+ doc => "ml-delete-calendar",
+ method => "DELETE",
+ parts => { calendar_id => {} },
+ paths =>
+ [ [ { calendar_id => 2 }, "_ml", "calendars", "{calendar_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_calendar_event' => {
+ doc => "ml-delete-calendar-event",
+ method => "DELETE",
+ parts => { calendar_id => {}, event_id => {} },
+ paths => [
+ [ { calendar_id => 2, event_id => 4 }, "_ml",
+ "calendars", "{calendar_id}",
+ "events", "{event_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_calendar_job' => {
+ doc => "ml-delete-calendar-job",
+ method => "DELETE",
+ parts => { calendar_id => {}, job_id => {} },
+ paths => [
+ [ { calendar_id => 2, job_id => 4 },
+ "_ml", "calendars", "{calendar_id}", "jobs", "{job_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_data_frame_analytics' => {
+ doc => "delete-dfanalytics",
+ method => "DELETE",
+ parts => { id => {} },
+ paths =>
+ [ [ { id => 3 }, "_ml", "data_frame", "analytics", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_datafeed' => {
+ doc => "ml-delete-datafeed",
+ method => "DELETE",
+ parts => { datafeed_id => {} },
+ paths =>
+ [ [ { datafeed_id => 2 }, "_ml", "datafeeds", "{datafeed_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ },
+ },
+
+ 'ml.delete_expired_data' => {
+ body => {},
+ doc => "ml-delete-expired-data",
+ method => "DELETE",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml", "_delete_expired_data", "{job_id}" ],
+ [ {}, "_ml", "_delete_expired_data" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ requests_per_second => "number",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_filter' => {
+ doc => "ml-delete-filter",
+ method => "DELETE",
+ parts => { filter_id => {} },
+ paths => [ [ { filter_id => 2 }, "_ml", "filters", "{filter_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_forecast' => {
+ doc => "ml-delete-forecast",
+ method => "DELETE",
+ parts => { forecast_id => {}, job_id => {} },
+ paths => [
+ [ { forecast_id => 4, job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_forecast", "{forecast_id}",
+ ],
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_forecast",
+ ],
+ ],
+ qs => {
+ allow_no_forecasts => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_job' => {
+ doc => "ml-delete-job",
+ method => "DELETE",
+ parts => { job_id => {} },
+ paths =>
+ [ [ { job_id => 2 }, "_ml", "anomaly_detectors", "{job_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'ml.delete_model_snapshot' => {
+ doc => "ml-delete-snapshot",
+ method => "DELETE",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_trained_model' => {
+ doc => "delete-trained-models",
+ method => "DELETE",
+ parts => { model_id => {} },
+ paths =>
+ [ [ { model_id => 2 }, "_ml", "trained_models", "{model_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.estimate_model_memory' => {
+ body => { required => 1 },
+ doc => "ml-apis",
+ method => "POST",
+ parts => {},
+ paths =>
+ [ [ {}, "_ml", "anomaly_detectors", "_estimate_model_memory" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.evaluate_data_frame' => {
+ body => { required => 1 },
+ doc => "evaluate-dfanalytics",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ml", "data_frame", "_evaluate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.explain_data_frame_analytics' => {
+ body => {},
+ doc => "explain-dfanalytics",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_explain"
+ ],
+ [ {}, "_ml", "data_frame", "analytics", "_explain" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.find_file_structure' => {
+ body => { required => 1 },
+ doc => "ml-find-file-structure",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ml", "find_file_structure" ] ],
+ qs => {
+ charset => "string",
+ column_names => "list",
+ delimiter => "string",
+ error_trace => "boolean",
+ explain => "boolean",
+ filter_path => "list",
+ format => "enum",
+ grok_pattern => "string",
+ has_header_row => "boolean",
+ human => "boolean",
+ line_merge_size_limit => "int",
+ lines_to_sample => "int",
+ quote => "string",
+ should_trim_fields => "boolean",
+ timeout => "time",
+ timestamp_field => "string",
+ timestamp_format => "string",
+ },
+ serialize => "bulk",
+ },
+
+ 'ml.flush_job' => {
+ body => {},
+ doc => "ml-flush-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_flush",
+ ],
+ ],
+ qs => {
+ advance_time => "string",
+ calc_interim => "boolean",
+ end => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ skip_time => "string",
+ start => "string",
+ },
+ },
+
+ 'ml.forecast' => {
+ doc => "ml-forecast",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_forecast",
+ ],
+ ],
+ qs => {
+ duration => "time",
+ error_trace => "boolean",
+ expires_in => "time",
+ filter_path => "list",
+ human => "boolean",
+ max_model_memory => "string",
+ },
+ },
+
+ 'ml.get_buckets' => {
+ body => {},
+ doc => "ml-get-bucket",
+ parts => { job_id => {}, timestamp => {} },
+ paths => [
+ [ { job_id => 2, timestamp => 5 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "buckets",
+ "{timestamp}",
+ ],
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "buckets",
+ ],
+ ],
+ qs => {
+ anomaly_score => "double",
+ desc => "boolean",
+ end => "string",
+ error_trace => "boolean",
+ exclude_interim => "boolean",
+ expand => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ sort => "string",
+ start => "string",
+ },
+ },
+
+ 'ml.get_calendar_events' => {
+ doc => "ml-get-calendar-event",
+ parts => { calendar_id => {} },
+ paths => [
+ [ { calendar_id => 2 }, "_ml",
+ "calendars", "{calendar_id}",
+ "events",
+ ],
+ ],
+ qs => {
+ end => "time",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ job_id => "string",
+ size => "int",
+ start => "string",
+ },
+ },
+
+ 'ml.get_calendars' => {
+ body => {},
+ doc => "ml-get-calendar",
+ parts => { calendar_id => {} },
+ paths => [
+ [ { calendar_id => 2 }, "_ml", "calendars", "{calendar_id}" ],
+ [ {}, "_ml", "calendars" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'ml.get_categories' => {
+ body => {},
+ doc => "ml-get-category",
+ parts => { category_id => {}, job_id => {} },
+ paths => [
+ [ { category_id => 5, job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "categories",
+ "{category_id}",
+ ],
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "categories",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ partition_field_value => "string",
+ size => "int",
+ },
+ },
+
+ 'ml.get_data_frame_analytics' => {
+ doc => "get-dfanalytics",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics", "{id}" ],
+ [ {}, "_ml", "data_frame", "analytics" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'ml.get_data_frame_analytics_stats' => {
+ doc => "get-dfanalytics-stats",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_stats"
+ ],
+ [ {}, "_ml", "data_frame", "analytics", "_stats" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ verbose => "boolean",
+ },
+ },
+
+ 'ml.get_datafeed_stats' => {
+ doc => "ml-get-datafeed-stats",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_stats",
+ ],
+ [ {}, "_ml", "datafeeds", "_stats" ],
+ ],
+ qs => {
+ allow_no_datafeeds => "boolean",
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_datafeeds' => {
+ doc => "ml-get-datafeed",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml", "datafeeds", "{datafeed_id}" ],
+ [ {}, "_ml", "datafeeds" ],
+ ],
+ qs => {
+ allow_no_datafeeds => "boolean",
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_filters' => {
+ doc => "ml-get-filter",
+ parts => { filter_id => {} },
+ paths => [
+ [ { filter_id => 2 }, "_ml", "filters", "{filter_id}" ],
+ [ {}, "_ml", "filters" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'ml.get_influencers' => {
+ body => {},
+ doc => "ml-get-influencer",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "influencers",
+ ],
+ ],
+ qs => {
+ desc => "boolean",
+ end => "string",
+ error_trace => "boolean",
+ exclude_interim => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ influencer_score => "double",
+ size => "int",
+ sort => "string",
+ start => "string",
+ },
+ },
+
+ 'ml.get_job_stats' => {
+ doc => "ml-get-job-stats",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_stats",
+ ],
+ [ {}, "_ml", "anomaly_detectors", "_stats" ],
+ ],
+ qs => {
+ allow_no_jobs => "boolean",
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_jobs' => {
+ doc => "ml-get-job",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml", "anomaly_detectors", "{job_id}" ],
+ [ {}, "_ml", "anomaly_detectors" ],
+ ],
+ qs => {
+ allow_no_jobs => "boolean",
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_model_snapshots' => {
+ body => {},
+ doc => "ml-get-snapshot",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ ],
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots",
+ ],
+ ],
+ qs => {
+ desc => "boolean",
+ end => "time",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ sort => "string",
+ start => "time",
+ },
+ },
+
+ 'ml.get_overall_buckets' => {
+ body => {},
+ doc => "ml-get-overall-buckets",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "overall_buckets",
+ ],
+ ],
+ qs => {
+ allow_no_jobs => "boolean",
+ allow_no_match => "boolean",
+ bucket_span => "string",
+ end => "string",
+ error_trace => "boolean",
+ exclude_interim => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ overall_score => "double",
+ start => "string",
+ top_n => "int",
+ },
+ },
+
+ 'ml.get_records' => {
+ body => {},
+ doc => "ml-get-record",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "records",
+ ],
+ ],
+ qs => {
+ desc => "boolean",
+ end => "string",
+ error_trace => "boolean",
+ exclude_interim => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ record_score => "double",
+ size => "int",
+ sort => "string",
+ start => "string",
+ },
+ },
+
+ 'ml.get_trained_models' => {
+ doc => "get-trained-models",
+ parts => { model_id => {} },
+ paths => [
+ [ { model_id => 2 }, "_ml", "trained_models", "{model_id}" ],
+ [ {}, "_ml", "trained_models" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ decompress_definition => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ include => "string",
+ include_model_definition => "boolean",
+ size => "int",
+ tags => "list",
+ },
+ },
+
+ 'ml.get_trained_models_stats' => {
+ doc => "get-trained-models-stats",
+ parts => { model_id => {} },
+ paths => [
+ [ { model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "_stats",
+ ],
+ [ {}, "_ml", "trained_models", "_stats" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'ml.info' => {
+ doc => "get-ml-info",
+ parts => {},
+ paths => [ [ {}, "_ml", "info" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.open_job' => {
+ doc => "ml-open-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_open"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.post_calendar_events' => {
+ body => { required => 1 },
+ doc => "ml-post-calendar-event",
+ method => "POST",
+ parts => { calendar_id => {} },
+ paths => [
+ [ { calendar_id => 2 }, "_ml",
+ "calendars", "{calendar_id}",
+ "events",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.post_data' => {
+ body => { required => 1 },
+ doc => "ml-post-data",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_data"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ reset_end => "string",
+ reset_start => "string",
+ },
+ serialize => "bulk",
+ },
+
+ 'ml.preview_datafeed' => {
+ doc => "ml-preview-datafeed",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_preview",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_calendar' => {
+ body => {},
+ doc => "ml-put-calendar",
+ method => "PUT",
+ parts => { calendar_id => {} },
+ paths =>
+ [ [ { calendar_id => 2 }, "_ml", "calendars", "{calendar_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_calendar_job' => {
+ doc => "ml-put-calendar-job",
+ method => "PUT",
+ parts => { calendar_id => {}, job_id => {} },
+ paths => [
+ [ { calendar_id => 2, job_id => 4 },
+ "_ml", "calendars", "{calendar_id}", "jobs", "{job_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_data_frame_analytics' => {
+ body => { required => 1 },
+ doc => "put-dfanalytics",
+ method => "PUT",
+ parts => { id => {} },
+ paths =>
+ [ [ { id => 3 }, "_ml", "data_frame", "analytics", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_datafeed' => {
+ body => { required => 1 },
+ doc => "ml-put-datafeed",
+ method => "PUT",
+ parts => { datafeed_id => {} },
+ paths =>
+ [ [ { datafeed_id => 2 }, "_ml", "datafeeds", "{datafeed_id}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'ml.put_filter' => {
+ body => { required => 1 },
+ doc => "ml-put-filter",
+ method => "PUT",
+ parts => { filter_id => {} },
+ paths => [ [ { filter_id => 2 }, "_ml", "filters", "{filter_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_job' => {
+ body => { required => 1 },
+ doc => "ml-put-job",
+ method => "PUT",
+ parts => { job_id => {} },
+ paths =>
+ [ [ { job_id => 2 }, "_ml", "anomaly_detectors", "{job_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_trained_model' => {
+ body => { required => 1 },
+ doc => "put-trained-models",
+ method => "PUT",
+ parts => { model_id => {} },
+ paths =>
+ [ [ { model_id => 2 }, "_ml", "trained_models", "{model_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.revert_model_snapshot' => {
+ body => {},
+ doc => "ml-revert-snapshot",
+ method => "POST",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ "_revert",
+ ],
+ ],
+ qs => {
+ delete_intervening_results => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.set_upgrade_mode' => {
+ doc => "ml-set-upgrade-mode",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ml", "set_upgrade_mode" ] ],
+ qs => {
+ enabled => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.start_data_frame_analytics' => {
+ body => {},
+ doc => "start-dfanalytics",
+ method => "POST",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_start"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.start_datafeed' => {
+ body => {},
+ doc => "ml-start-datafeed",
+ method => "POST",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_start",
+ ],
+ ],
+ qs => {
+ end => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ start => "string",
+ timeout => "time",
+ },
+ },
+
+ 'ml.stop_data_frame_analytics' => {
+ body => {},
+ doc => "stop-dfanalytics",
+ method => "POST",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics", "{id}",
+ "_stop"
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.stop_datafeed' => {
+ body => {},
+ doc => "ml-stop-datafeed",
+ method => "POST",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_stop",
+ ],
+ ],
+ qs => {
+ allow_no_datafeeds => "boolean",
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.update_data_frame_analytics' => {
+ body => { required => 1 },
+ doc => "update-dfanalytics",
+ method => "POST",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_update"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.update_datafeed' => {
+ body => { required => 1 },
+ doc => "ml-update-datafeed",
+ method => "POST",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_update",
+ ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'ml.update_filter' => {
+ body => { required => 1 },
+ doc => "ml-update-filter",
+ method => "POST",
+ parts => { filter_id => {} },
+ paths => [
+ [ { filter_id => 2 }, "_ml", "filters", "{filter_id}",
+ "_update"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.update_job' => {
+ body => { required => 1 },
+ doc => "ml-update-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_update",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.update_model_snapshot' => {
+ body => { required => 1 },
+ doc => "ml-update-snapshot",
+ method => "POST",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ "_update",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.upgrade_job_snapshot' => {
+ doc => "ml-upgrade-job-model-snapshot",
+ method => "POST",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ "_upgrade",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'ml.validate' => {
+ body => { required => 1 },
+ doc => "ml-jobs",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ml", "anomaly_detectors", "_validate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.validate_detector' => {
+ body => { required => 1 },
+ doc => "ml-jobs",
+ method => "POST",
+ parts => {},
+ paths =>
+ [ [ {}, "_ml", "anomaly_detectors", "_validate", "detector" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'monitoring.bulk' => {
+ body => { required => 1 },
+ doc => "monitor-elasticsearch-cluster",
+ method => "POST",
+ parts => { type => {} },
+ paths => [
+ [ { type => 1 }, "_monitoring", "{type}", "bulk" ],
+ [ {}, "_monitoring", "bulk" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ interval => "string",
+ system_api_version => "string",
+ system_id => "string",
+ },
+ serialize => "bulk",
+ },
+
+ 'nodes.hot_threads' => {
+ doc => "cluster-nodes-hot-threads",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 1 }, "_nodes", "{node_id}", "hot_threads" ],
+ [ {}, "_nodes", "hot_threads" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_idle_threads => "boolean",
+ interval => "time",
+ snapshots => "number",
+ threads => "number",
+ timeout => "time",
+ type => "enum",
+ },
+ },
+
+ 'nodes.info' => {
+ doc => "cluster-nodes-info",
+ parts => { metric => { multi => 1 }, node_id => { multi => 1 } },
+ paths => [
+ [ { metric => 2, node_id => 1 }, "_nodes",
+ "{node_id}", "{metric}",
+ ],
+ [ { metric => 1 }, "_nodes", "{metric}" ],
+ [ { node_id => 1 }, "_nodes", "{node_id}" ],
+ [ {}, "_nodes" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'nodes.reload_secure_settings' => {
+ body => {},
+ doc => "",
+ method => "POST",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 1 }, "_nodes",
+ "{node_id}", "reload_secure_settings",
+ ],
+ [ {}, "_nodes", "reload_secure_settings" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'nodes.stats' => {
+ doc => "cluster-nodes-stats",
+ parts => {
+ index_metric => { multi => 1 },
+ metric => { multi => 1 },
+ node_id => { multi => 1 },
+ },
+ paths => [
+ [ { index_metric => 4, metric => 3, node_id => 1 },
+ "_nodes", "{node_id}", "stats", "{metric}", "{index_metric}",
+ ],
+ [ { index_metric => 3, metric => 2 }, "_nodes",
+ "stats", "{metric}",
+ "{index_metric}",
+ ],
+ [ { metric => 3, node_id => 1 }, "_nodes",
+ "{node_id}", "stats",
+ "{metric}",
+ ],
+ [ { metric => 2 }, "_nodes", "stats", "{metric}" ],
+ [ { node_id => 1 }, "_nodes", "{node_id}", "stats" ],
+ [ {}, "_nodes", "stats" ],
+ ],
+ qs => {
+ completion_fields => "list",
+ error_trace => "boolean",
+ fielddata_fields => "list",
+ fields => "list",
+ filter_path => "list",
+ groups => "boolean",
+ human => "boolean",
+ include_segment_file_sizes => "boolean",
+ level => "enum",
+ timeout => "time",
+ types => "list",
+ },
+ },
+
+ 'nodes.usage' => {
+ doc => "cluster-nodes-usage",
+ parts => { metric => { multi => 1 }, node_id => { multi => 1 } },
+ paths => [
+ [ { metric => 3, node_id => 1 }, "_nodes",
+ "{node_id}", "usage",
+ "{metric}",
+ ],
+ [ { metric => 2 }, "_nodes", "usage", "{metric}" ],
+ [ { node_id => 1 }, "_nodes", "{node_id}", "usage" ],
+ [ {}, "_nodes", "usage" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'rollup.delete_job' => {
+ doc => "rollup-delete-job",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_rollup", "job", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.get_jobs' => {
+ doc => "rollup-get-job",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_rollup", "job", "{id}" ],
+ [ {}, "_rollup", "job" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.get_rollup_caps' => {
+ doc => "rollup-get-rollup-caps",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_rollup", "data", "{id}" ],
+ [ {}, "_rollup", "data" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.get_rollup_index_caps' => {
+ doc => "rollup-get-rollup-index-caps",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_rollup", "data" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.put_job' => {
+ body => { required => 1 },
+ doc => "rollup-put-job",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_rollup", "job", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.rollup' => {
+ body => { required => 1 },
+ doc => "rollup-api",
+ method => "POST",
+ parts =>
+ { index => { required => 1 }, rollup_index => { required => 1 } },
+ paths => [
+ [ { index => 0, rollup_index => 2 }, "{index}",
+ "_rollup", "{rollup_index}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.rollup_search' => {
+ body => { required => 1 },
+ doc => "rollup-search",
+ parts => { index => { multi => 1 }, type => {} },
+ paths => [
+ [ { index => 0, type => 1 }, "{index}",
+ "{type}", "_rollup_search",
+ ],
+ [ { index => 0 }, "{index}", "_rollup_search" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ rest_total_hits_as_int => "boolean",
+ typed_keys => "boolean",
+ },
+ },
+
+ 'rollup.start_job' => {
+ doc => "rollup-start-job",
+ method => "POST",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_rollup", "job", "{id}", "_start" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.stop_job' => {
+ doc => "rollup-stop-job",
+ method => "POST",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_rollup", "job", "{id}", "_stop" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'searchable_snapshots.clear_cache' => {
+ doc => "searchable-snapshots-apis",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}",
+ "_searchable_snapshots", "cache",
+ "clear",
+ ],
+ [ {}, "_searchable_snapshots", "cache", "clear" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'searchable_snapshots.mount' => {
+ body => { required => 1 },
+ doc => "searchable-snapshots-api-mount-snapshot",
+ method => "POST",
+ parts => { repository => {}, snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ "_mount",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'searchable_snapshots.repository_stats' => {
+ doc => "searchable-snapshots-apis",
+ parts => { repository => {} },
+ paths => [
+ [ { repository => 1 }, "_snapshot", "{repository}", "_stats" ]
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'searchable_snapshots.stats' => {
+ doc => "searchable-snapshots-apis",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_searchable_snapshots", "stats" ],
+ [ {}, "_searchable_snapshots", "stats" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.authenticate' => {
+ doc => "security-api-authenticate",
+ parts => {},
+ paths => [ [ {}, "_security", "_authenticate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.change_password' => {
+ body => { required => 1 },
+ doc => "security-api-change-password",
+ method => "PUT",
+ parts => { username => {} },
+ paths => [
+ [ { username => 2 }, "_security",
+ "user", "{username}",
+ "_password",
+ ],
+ [ {}, "_security", "user", "_password" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.clear_api_key_cache' => {
+ doc => "security-api-clear-api-key-cache",
+ method => "POST",
+ parts => { ids => { multi => 1 } },
+ paths => [
+ [ { ids => 2 }, "_security", "api_key", "{ids}", "_clear_cache" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.clear_cached_privileges' => {
+ doc => "security-api-clear-privilege-cache",
+ method => "POST",
+ parts => { application => { multi => 1 } },
+ paths => [
+ [ { application => 2 }, "_security",
+ "privilege", "{application}",
+ "_clear_cache",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.clear_cached_realms' => {
+ doc => "security-api-clear-cache",
+ method => "POST",
+ parts => { realms => { multi => 1 } },
+ paths => [
+ [ { realms => 2 }, "_security", "realm", "{realms}",
+ "_clear_cache",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ usernames => "list",
+ },
+ },
+
+ 'security.clear_cached_roles' => {
+ doc => "security-api-clear-role-cache",
+ method => "POST",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_security", "role", "{name}", "_clear_cache" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.create_api_key' => {
+ body => { required => 1 },
+ doc => "security-api-create-api-key",
+ method => "PUT",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_privileges' => {
+ doc => "security-api-delete-privilege",
+ method => "DELETE",
+ parts => { application => {}, name => {} },
+ paths => [
+ [ { application => 2, name => 3 }, "_security",
+ "privilege", "{application}",
+ "{name}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_role' => {
+ doc => "security-api-delete-role",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_security", "role", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_role_mapping' => {
+ doc => "security-api-delete-role-mapping",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_security", "role_mapping", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_user' => {
+ doc => "security-api-delete-user",
+ method => "DELETE",
+ parts => { username => {} },
+ paths => [ [ { username => 2 }, "_security", "user", "{username}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.disable_user' => {
+ doc => "security-api-disable-user",
+ method => "PUT",
+ parts => { username => {} },
+ paths => [
+ [ { username => 2 }, "_security",
+ "user", "{username}",
+ "_disable"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.enable_user' => {
+ doc => "security-api-enable-user",
+ method => "PUT",
+ parts => { username => {} },
+ paths => [
+ [ { username => 2 }, "_security",
+ "user", "{username}",
+ "_enable"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.get_api_key' => {
+ doc => "security-api-get-api-key",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ id => "string",
+ name => "string",
+ owner => "boolean",
+ realm_name => "string",
+ username => "string",
+ },
+ },
+
+ 'security.get_builtin_privileges' => {
+ doc => "security-api-get-builtin-privileges",
+ parts => {},
+ paths => [ [ {}, "_security", "privilege", "_builtin" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_privileges' => {
+ doc => "security-api-get-privileges",
+ parts => { application => {}, name => {} },
+ paths => [
+ [ { application => 2, name => 3 }, "_security",
+ "privilege", "{application}",
+ "{name}",
+ ],
+ [ { application => 2 }, "_security",
+ "privilege", "{application}"
+ ],
+ [ {}, "_security", "privilege" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_role' => {
+ doc => "security-api-get-role",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_security", "role", "{name}" ],
+ [ {}, "_security", "role" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_role_mapping' => {
+ doc => "security-api-get-role-mapping",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_security", "role_mapping", "{name}" ],
+ [ {}, "_security", "role_mapping" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_token' => {
+ body => { required => 1 },
+ doc => "security-api-get-token",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "oauth2", "token" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_user' => {
+ doc => "security-api-get-user",
+ parts => { username => { multi => 1 } },
+ paths => [
+ [ { username => 2 }, "_security", "user", "{username}" ],
+ [ {}, "_security", "user" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_user_privileges' => {
+ doc => "security-api-get-privileges",
+ parts => {},
+ paths => [ [ {}, "_security", "user", "_privileges" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.grant_api_key' => {
+ body => { required => 1 },
+ doc => "security-api-grant-api-key",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key", "grant" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.has_privileges' => {
+ body => { required => 1 },
+ doc => "security-api-has-privileges",
+ parts => { user => {} },
+ paths => [
+ [ { user => 2 }, "_security",
+ "user", "{user}",
+ "_has_privileges"
+ ],
+ [ {}, "_security", "user", "_has_privileges" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.invalidate_api_key' => {
+ body => { required => 1 },
+ doc => "security-api-invalidate-api-key",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.invalidate_token' => {
+ body => { required => 1 },
+ doc => "security-api-invalidate-token",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_security", "oauth2", "token" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.put_privileges' => {
+ body => { required => 1 },
+ doc => "security-api-put-privileges",
+ method => "PUT",
+ parts => {},
+ paths => [ [ {}, "_security", "privilege" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.put_role' => {
+ body => { required => 1 },
+ doc => "security-api-put-role",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_security", "role", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.put_role_mapping' => {
+ body => { required => 1 },
+ doc => "security-api-put-role-mapping",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_security", "role_mapping", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.put_user' => {
+ body => { required => 1 },
+ doc => "security-api-put-user",
+ method => "PUT",
+ parts => { username => {} },
+ paths => [ [ { username => 2 }, "_security", "user", "{username}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'slm.delete_lifecycle' => {
+ doc => "slm-api-delete-policy",
+ method => "DELETE",
+ parts => { policy_id => {} },
+ paths => [ [ { policy_id => 2 }, "_slm", "policy", "{policy_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.execute_lifecycle' => {
+ doc => "slm-api-execute-lifecycle",
+ method => "PUT",
+ parts => { policy_id => {} },
+ paths => [
+ [ { policy_id => 2 }, "_slm",
+ "policy", "{policy_id}",
+ "_execute"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.execute_retention' => {
+ doc => "slm-api-execute-retention",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_slm", "_execute_retention" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.get_lifecycle' => {
+ doc => "slm-api-get-policy",
+ parts => { policy_id => { multi => 1 } },
+ paths => [
+ [ { policy_id => 2 }, "_slm", "policy", "{policy_id}" ],
+ [ {}, "_slm", "policy" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.get_stats' => {
+ doc => "slm-api-get-stats",
+ parts => {},
+ paths => [ [ {}, "_slm", "stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.get_status' => {
+ doc => "slm-api-get-status",
+ parts => {},
+ paths => [ [ {}, "_slm", "status" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.put_lifecycle' => {
+ body => {},
+ doc => "slm-api-put-policy",
+ method => "PUT",
+ parts => { policy_id => {} },
+ paths => [ [ { policy_id => 2 }, "_slm", "policy", "{policy_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.start' => {
+ doc => "slm-api-start",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_slm", "start" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.stop' => {
+ doc => "slm-api-stop",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_slm", "stop" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'snapshot.cleanup_repository' => {
+ doc => "clean-up-snapshot-repo-api",
+ method => "POST",
+ parts => { repository => {} },
+ paths => [
+ [ { repository => 1 }, "_snapshot", "{repository}", "_cleanup" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'snapshot.clone' => {
+ body => { required => 1 },
+ doc => "modules-snapshots",
+ method => "PUT",
+ parts => { repository => {}, snapshot => {}, target_snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2, target_snapshot => 4 },
+ "_snapshot",
+ "{repository}",
+ "{snapshot}",
+ "_clone",
+ "{target_snapshot}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'snapshot.create' => {
+ body => {},
+ doc => "modules-snapshots",
+ method => "PUT",
+ parts => { repository => {}, snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'snapshot.create_repository' => {
+ body => { required => 1 },
+ doc => "modules-snapshots",
+ method => "PUT",
+ parts => { repository => {} },
+ paths => [ [ { repository => 1 }, "_snapshot", "{repository}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ verify => "boolean",
+ },
+ },
+
+ 'snapshot.delete' => {
+ doc => "modules-snapshots",
+ method => "DELETE",
+ parts => { repository => {}, snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'snapshot.delete_repository' => {
+ doc => "modules-snapshots",
+ method => "DELETE",
+ parts => { repository => { multi => 1 } },
+ paths => [ [ { repository => 1 }, "_snapshot", "{repository}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'snapshot.get' => {
+ doc => "modules-snapshots",
+ parts => { repository => {}, snapshot => { multi => 1 } },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ verbose => "boolean",
+ },
+ },
+
+ 'snapshot.get_repository' => {
+ doc => "modules-snapshots",
+ parts => { repository => { multi => 1 } },
+ paths => [
+ [ { repository => 1 }, "_snapshot", "{repository}" ],
+ [ {}, "_snapshot" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'snapshot.restore' => {
+ body => {},
+ doc => "modules-snapshots",
+ method => "POST",
+ parts => { repository => {}, snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ "_restore",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'snapshot.status' => {
+ doc => "modules-snapshots",
+ parts => { repository => {}, snapshot => { multi => 1 } },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ "_status",
+ ],
+ [ { repository => 1 }, "_snapshot", "{repository}", "_status" ],
+ [ {}, "_snapshot", "_status" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'snapshot.verify_repository' => {
+ doc => "modules-snapshots",
+ method => "POST",
+ parts => { repository => {} },
+ paths => [
+ [ { repository => 1 }, "_snapshot", "{repository}", "_verify" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'sql.clear_cursor' => {
+ body => { required => 1 },
+ doc => "sql-pagination",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_sql", "close" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'sql.query' => {
+ body => { required => 1 },
+ doc => "sql-rest-overview",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_sql" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ human => "boolean",
+ },
+ },
+
+ 'sql.translate' => {
+ body => { required => 1 },
+ doc => "sql-translate",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_sql", "translate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ssl.certificates' => {
+ doc => "security-api-ssl",
+ parts => {},
+ paths => [ [ {}, "_ssl", "certificates" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'tasks.cancel' => {
+ doc => "tasks",
+ method => "POST",
+ parts => { task_id => {} },
+ paths => [
+ [ { task_id => 1 }, "_tasks", "{task_id}", "_cancel" ],
+ [ {}, "_tasks", "_cancel" ],
+ ],
+ qs => {
+ actions => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ nodes => "list",
+ parent_task_id => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'tasks.get' => {
+ doc => "tasks",
+ parts => { task_id => {} },
+ paths => [ [ { task_id => 1 }, "_tasks", "{task_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'tasks.list' => {
+ doc => "tasks",
+ parts => {},
+ paths => [ [ {}, "_tasks" ] ],
+ qs => {
+ actions => "list",
+ detailed => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ group_by => "enum",
+ human => "boolean",
+ nodes => "list",
+ parent_task_id => "string",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'transform.delete_transform' => {
+ doc => "delete-transform",
+ method => "DELETE",
+ parts => { transform_id => {} },
+ paths =>
+ [ [ { transform_id => 1 }, "_transform", "{transform_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ },
+ },
+
+ 'transform.get_transform' => {
+ doc => "get-transform",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform", "{transform_id}" ],
+ [ {}, "_transform" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'transform.get_transform_stats' => {
+ doc => "get-transform-stats",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform",
+ "{transform_id}", "_stats"
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ size => "number",
+ },
+ },
+
+ 'transform.preview_transform' => {
+ body => { required => 1 },
+ doc => "preview-transform",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_transform", "_preview" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'transform.put_transform' => {
+ body => { required => 1 },
+ doc => "put-transform",
+ method => "PUT",
+ parts => { transform_id => {} },
+ paths =>
+ [ [ { transform_id => 1 }, "_transform", "{transform_id}" ] ],
+ qs => {
+ defer_validation => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'transform.start_transform' => {
+ doc => "start-transform",
+ method => "POST",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform",
+ "{transform_id}", "_start"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'transform.stop_transform' => {
+ doc => "stop-transform",
+ method => "POST",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform", "{transform_id}",
+ "_stop"
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ wait_for_checkpoint => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'transform.update_transform' => {
+ body => { required => 1 },
+ doc => "update-transform",
+ method => "POST",
+ parts => { transform_id => { required => 1 } },
+ paths => [
+ [ { transform_id => 1 }, "_transform",
+ "{transform_id}", "_update",
+ ],
+ ],
+ qs => {
+ defer_validation => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'watcher.ack_watch' => {
+ doc => "watcher-api-ack-watch",
+ method => "PUT",
+ parts => { action_id => { multi => 1 }, watch_id => {} },
+ paths => [
+ [ { action_id => 4, watch_id => 2 }, "_watcher",
+ "watch", "{watch_id}",
+ "_ack", "{action_id}",
+ ],
+ [ { watch_id => 2 }, "_watcher", "watch", "{watch_id}", "_ack" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.activate_watch' => {
+ doc => "watcher-api-activate-watch",
+ method => "PUT",
+ parts => { watch_id => {} },
+ paths => [
+ [ { watch_id => 2 }, "_watcher",
+ "watch", "{watch_id}",
+ "_activate",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.deactivate_watch' => {
+ doc => "watcher-api-deactivate-watch",
+ method => "PUT",
+ parts => { watch_id => {} },
+ paths => [
+ [ { watch_id => 2 }, "_watcher",
+ "watch", "{watch_id}",
+ "_deactivate",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.delete_watch' => {
+ doc => "watcher-api-delete-watch",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_watcher", "watch", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.execute_watch' => {
+ body => {},
+ doc => "watcher-api-execute-watch",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_watcher", "watch", "{id}", "_execute" ],
+ [ {}, "_watcher", "watch", "_execute" ],
+ ],
+ qs => {
+ debug => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'watcher.get_watch' => {
+ doc => "watcher-api-get-watch",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_watcher", "watch", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.put_watch' => {
+ body => {},
+ doc => "watcher-api-put-watch",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_watcher", "watch", "{id}" ] ],
+ qs => {
+ active => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ version => "number",
+ },
+ },
+
+ 'watcher.query_watches' => {
+ body => {},
+ doc => "watcher-api-query-watches",
+ parts => {},
+ paths => [ [ {}, "_watcher", "_query", "watches" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.start' => {
+ doc => "watcher-api-start",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_watcher", "_start" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.stats' => {
+ doc => "watcher-api-stats",
+ parts => { metric => { multi => 1 } },
+ paths => [
+ [ { metric => 2 }, "_watcher", "stats", "{metric}" ],
+ [ {}, "_watcher", "stats" ],
+ ],
+ qs => {
+ emit_stacktraces => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'watcher.stop' => {
+ doc => "watcher-api-stop",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_watcher", "_stop" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'xpack.info' => {
+ doc => "info-api",
+ parts => {},
+ paths => [ [ {}, "_xpack" ] ],
+ qs => {
+ accept_enterprise => "boolean",
+ categories => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'xpack.usage' => {
+ doc => "usage-api",
+ parts => {},
+ paths => [ [ {}, "_xpack", "usage" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+#=== AUTOGEN - END ===
+
+);
+
+__PACKAGE__->_qs_init( \%API );
+1;
+
+__END__
+
+# ABSTRACT: This class contains the spec for the Elasticsearch APIs
+
+=head1 DESCRIPTION
+
+All of the Elasticsearch APIs are defined in this role. The example given below
+is the definition for the L<Search::Elasticsearch::Client::7_0::Direct/index()> method:
+
+ 'index' => {
+ body => { required => 1 },
+ doc => "docs-index_",
+ method => "POST",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 }, "{index}",
+ "{type}", "{id}"
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ],
+ [ { index => 0, type => 1 }, "{index}", "{type}" ],
+ [ { index => 0 }, "{index}", "_doc" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ op_type => "enum",
+ pipeline => "string",
+ refresh => "enum",
+ require_alias => "boolean",
+ routing => "string",
+ timeout => "time",
+ version => "number",
+ version_type => "enum",
+ wait_for_active_shards => "string",
+ },
+ }
+
+These definitions can be used by different L<Search::Elasticsearch::Role::Client>
+implementations to provide distinct user interfaces.
+
+=head1 METHODS
+
+=head2 C<api()>
+
+ $defn = $api->api($name);
+
+The only method in this class is the C<api()> method which takes the name
+of the I<action> and returns its definition. Actions in the
+C<indices> or C<cluster> namespace use the namespace as a prefix, eg:
+
+ $defn = $e->api('indices.create');
+ $defn = $e->api('cluster.node_stats');
+
+=head1 SEE ALSO
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Role::API>
+
+=item *
+
+L<Search::Elasticsearch::Client::7_0::Direct>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/2_0/Role/Bulk.pm b/lib/Search/Elasticsearch/Client/7_0/Role/Bulk.pm
similarity index 83%
rename from lib/Search/Elasticsearch/Client/2_0/Role/Bulk.pm
rename to lib/Search/Elasticsearch/Client/7_0/Role/Bulk.pm
index 27330c2..c834f94 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Role/Bulk.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Role/Bulk.pm
@@ -1,5 +1,22 @@
-package Search::Elasticsearch::Client::2_0::Role::Bulk;
-$Search::Elasticsearch::Client::2_0::Role::Bulk::VERSION = '6.81';
+# 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 Search::Elasticsearch::Client::7_0::Role::Bulk;
+
use Moo::Role;
requires 'add_action', 'flush';
@@ -33,16 +50,6 @@ our %Actions = (
'delete' => 1
);
-around BUILDARGS => sub {
- my $orig = shift;
- my ( $class, $params ) = parse_params(@_);
- my $es = $params->{es} or throw( 'Param', 'Missing required param <es>' );
- $params->{_metadata_params} = $es->api('bulk.metadata')->{params};
- $params->{_update_params} = $es->api('bulk.update')->{params};
- $params->{_required_params} = $es->api('bulk.required')->{params};
- return $class->$orig($params);
-};
-
#===================================
sub _build__serializer { shift->es->transport->serializer }
#===================================
@@ -62,10 +69,14 @@ sub _build_on_error {
sub BUILDARGS {
#===================================
my ( $class, $params ) = parse_params(@_);
+ my $es = $params->{es} or throw( 'Param', 'Missing required param <es>' );
+ $params->{_metadata_params} = $es->api('bulk.metadata')->{params};
+ $params->{_update_params} = $es->api('bulk.update')->{params};
+ $params->{_required_params} = $es->api('bulk.required')->{params};
+ my $bulk_spec = $es->api('bulk');
my %args;
- for (qw(index type consistency fields refresh replication routing timeout))
- {
- $args{$_} = $params->{$_}
+ for ( keys %{ $bulk_spec->{qs} }, keys %{ $bulk_spec->{parts} } ) {
+ $args{$_} = delete $params->{$_}
if exists $params->{$_};
}
$params->{_bulk_args} = \%args;
@@ -100,7 +111,7 @@ sub update {
sub create_docs {
#===================================
my $self = shift;
- $self->add_action( map { ( 'create' => { _source => $_ } ) } @_ );
+ $self->add_action( map { ( 'create' => { source => $_ } ) } @_ );
}
#===================================
@@ -127,12 +138,10 @@ sub _encode_action {
my $params = {%$orig};
my $serializer = $self->_serializer;
- for ( @{ $self->_metadata_params } ) {
- my $val
- = exists $params->{$_} ? delete $params->{$_}
- : exists $params->{"_$_"} ? delete $params->{"_$_"}
- : next;
- $metadata{"_$_"} = $val;
+ my $meta_params = $self->_metadata_params;
+ for ( keys %$meta_params ) {
+ next unless exists $params->{$_};
+ $metadata{ $meta_params->{$_} } = delete $params->{$_};
}
for ( @{ $self->_required_params } ) {
@@ -148,14 +157,10 @@ sub _encode_action {
}
}
elsif ( $action ne 'delete' ) {
- $source
- = delete $params->{_source}
- || delete $params->{source}
- || throw(
- 'Param',
+ $source = delete $params->{source}
+ || throw( 'Param',
"Missing <source> for action <$action>: "
- . $serializer->encode($orig)
- );
+ . $serializer->encode($orig) );
}
throw( "Unknown params <"
@@ -272,32 +277,4 @@ sub _doc_transformer {
1;
-# ABSTRACT: Provides common functionality to L<Elasticseach::Client::2_0::Bulk> and L<Search::Elasticsearch::Client::2_0::Async::Bulk>
-
-__END__
-
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Role::Bulk - Provides common functionality to L<Elasticseach::Client::2_0::Bulk> and L<Search::Elasticsearch::Client::2_0::Async::Bulk>
-
-=head1 VERSION
-
-version 6.81
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
+# ABSTRACT: Provides common functionality to L<Elasticseach::Client::7_0::Bulk> and L<Search::Elasticsearch::Client::7_0::Async::Bulk>
diff --git a/lib/Search/Elasticsearch/Client/2_0/Role/Scroll.pm b/lib/Search/Elasticsearch/Client/7_0/Role/Scroll.pm
similarity index 58%
rename from lib/Search/Elasticsearch/Client/2_0/Role/Scroll.pm
rename to lib/Search/Elasticsearch/Client/7_0/Role/Scroll.pm
index 4837ae9..9d67a0f 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Role/Scroll.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Role/Scroll.pm
@@ -1,14 +1,29 @@
-package Search::Elasticsearch::Client::2_0::Role::Scroll;
-$Search::Elasticsearch::Client::2_0::Role::Scroll::VERSION = '6.81';
+# 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 Search::Elasticsearch::Client::7_0::Role::Scroll;
+
use Moo::Role;
requires 'finish';
use Search::Elasticsearch::Util qw(parse_params throw);
use Devel::GlobalDestruction;
use namespace::clean;
-
has 'es' => ( is => 'ro', required => 1 );
has 'scroll' => ( is => 'ro' );
-has 'scroll_in_qs' => ( is => 'ro' );
has 'total' => ( is => 'rwp' );
has 'max_score' => ( is => 'rwp' );
has 'facets' => ( is => 'rwp' );
@@ -31,12 +46,7 @@ sub scroll_request {
if $self->_pid != $$;
my %args = ( scroll => $self->scroll );
- if ( $self->scroll_in_qs ) {
- $args{scroll_id} = $self->_scroll_id;
- }
- else {
- $args{body} = $self->_scroll_id;
- }
+ $args{body} = { scroll_id => $self->_scroll_id };
$self->es->scroll(%args);
}
@@ -50,32 +60,4 @@ sub DEMOLISH {
1;
-# ABSTRACT: Provides common functionality to L<Search::Elasticsearch::Client::2_0::Scroll> and L<Search::Elasticsearch::Client::2_0::Async::Scroll>
-
-__END__
-
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Role::Scroll - Provides common functionality to L<Search::Elasticsearch::Client::2_0::Scroll> and L<Search::Elasticsearch::Client::2_0::Async::Scroll>
-
-=head1 VERSION
-
-version 6.81
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
+# ABSTRACT: Provides common functionality to L<Search::Elasticsearch::Client::7_0::Scroll> and L<Search::Elasticsearch::Client::7_0::Async::Scroll>
diff --git a/lib/Search/Elasticsearch/Client/2_0/Scroll.pm b/lib/Search/Elasticsearch/Client/7_0/Scroll.pm
similarity index 68%
rename from lib/Search/Elasticsearch/Client/2_0/Scroll.pm
rename to lib/Search/Elasticsearch/Client/7_0/Scroll.pm
index fa32c66..e94f9f6 100644
--- a/lib/Search/Elasticsearch/Client/2_0/Scroll.pm
+++ b/lib/Search/Elasticsearch/Client/7_0/Scroll.pm
@@ -1,5 +1,22 @@
-package Search::Elasticsearch::Client::2_0::Scroll;
-$Search::Elasticsearch::Client::2_0::Scroll::VERSION = '6.81';
+# 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 Search::Elasticsearch::Client::7_0::Scroll;
+
use Moo;
use Search::Elasticsearch::Util qw(parse_params throw);
use namespace::clean;
@@ -7,7 +24,7 @@ use namespace::clean;
has '_buffer' => ( is => 'ro' );
with 'Search::Elasticsearch::Role::Is_Sync',
- 'Search::Elasticsearch::Client::2_0::Role::Scroll';
+ 'Search::Elasticsearch::Client::7_0::Role::Scroll';
#===================================
sub BUILDARGS {
@@ -16,19 +33,16 @@ sub BUILDARGS {
my $es = delete $params->{es};
my $scroll = $params->{scroll} ||= '1m';
- throw( 'Param',
- 'The (scroll_in_body) parameter has been replaced by (scroll_in_qs)' )
- if exists $params->{scroll_in_body};
-
- my $scroll_in_qs = delete $params->{scroll_in_qs};
my $results = $es->search($params);
my $total = $results->{hits}{total};
+ if (ref $total) {
+ $total = $total->{value}
+ }
return {
es => $es,
scroll => $scroll,
- scroll_in_qs => $scroll_in_qs,
aggregations => $results->{aggregations},
facets => $results->{facets},
suggest => $results->{suggest},
@@ -106,27 +120,16 @@ sub finish {
my $scroll_id = $self->_scroll_id or return;
$self->_clear_scroll_id;
- my %args
- = $self->scroll_in_qs
- ? ( scroll_id => $scroll_id )
- : ( body => $scroll_id );
+ my %args = ( body => { scroll_id => $scroll_id } );
eval { $self->es->clear_scroll(%args) };
return 1;
}
1;
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-Search::Elasticsearch::Client::2_0::Scroll - A helper module for scrolled searches
-
-=head1 VERSION
+__END__
-version 6.81
+# ABSTRACT: A helper module for scrolled searches
=head1 SYNOPSIS
@@ -136,8 +139,11 @@ version 6.81
my $scroll = $es->scroll_helper(
index => 'my_index',
- search_type => 'scan',
- size => 500
+ body => {
+ query => {...},
+ size => 1000,
+ sort => '_doc'
+ }
);
say "Total hits: ". $scroll->total;
@@ -153,22 +159,20 @@ until there are no more matching results, much like a cursor in an SQL
database.
Unlike paginating through results (with the C<from> parameter in
-L<search()|Search::Elasticsearch::Client::2_0::Direct/search()>),
+L<search()|Search::Elasticsearch::Client::7_0::Direct/search()>),
scrolled searches take a snapshot of the current state of the index. Even
if you keep adding new documents to the index or updating existing documents,
a scrolled search will only see the index as it was when the search began.
This module is a helper utility that wraps the functionality of the
-L<search()|Search::Elasticsearch::Client::2_0::Direct/search()> and
-L<scroll()|Search::Elasticsearch::Client::2_0::Direct/scroll()> methods to make
+L<search()|Search::Elasticsearch::Client::7_0::Direct/search()> and
+L<scroll()|Search::Elasticsearch::Client::7_0::Direct/scroll()> methods to make
them easier to use.
-B<IMPORTANT>: Deep scrolling can be expensive. See L</DEEP SCROLLING>
-for more.
-
-This class does L<Search::Elasticsearch::Client::2_0::Role::Scroll> and
+This class does L<Search::Elasticsearch::Client::7_0::Role::Scroll> and
L<Search::Elasticsearch::Role::Is_Sync>.
+
=head1 USE CASES
There are two primary use cases:
@@ -202,6 +206,7 @@ list, and return results grouped by C<thread_id>:
}
+
=head2 Extracting all documents
Often you will want to extract all (or a subset of) documents in an index.
@@ -214,14 +219,14 @@ C<client_id>:
my $scroll = $es->scroll_helper(
index => 'my_index',
- search_type => 'scan', # important!
- size => 500,
+ size => 1000,
body => {
query => {
match => {
client_id => 123
}
- }
+ },
+ sort => '_doc'
}
);
@@ -231,42 +236,8 @@ C<client_id>:
Very often the I<something> that you will want to do with these results
involves bulk-indexing them into a new index. The easiest way to
-marry a scrolled search with bulk indexing is to use the
-L<Search::Elasticsearch::Client::2_0::Bulk/reindex()> method.
-
-=head1 DEEP SCROLLING
-
-Deep scrolling (and deep pagination) are very expensive in a distributed
-environment, and the reason they are expensive is that results need to
-be sorted in a global order.
-
-For example, if we have an index with 5 shards, and we request the first
-10 results, each shard has to return its top 10, and then the I<requesting
-node> (the node that is handling the search request) has to resort these
-50 results to return a global top 10. Now, if we request page 1,000
-(ie results 10,001 .. 10,010), then each shard has to return 10,010 results,
-and the requesting node has to sort through 50,050 results just to return
-10 of them!
-
-You can see how this can get very heavy very quickly. This is the reason that
-web search engines never return more than 1,000 results.
-
-=head2 Disable sorting for efficient scrolling
-
-The problem with deep scrolling is the sorting phase. If we disable sorting,
-then we can happily scroll through millions of documents efficiently. The
-way to do this is to set C<search_type> to C<scan>:
-
- my $scroll = $es->scroll_helper(
- search_type => 'scan',
- size => 500,
- );
-
-Scanning disables sorting and will just return C<size> results from each
-shard until there are no more results to return. B<Note>: this means
-that, when querying an index with 5 shards, the scrolled search
-will pull C<size * 5> results at a time. If you have large documents or
-are memory constrained, you will need to take this into account.
+do this is to use the built-in L<Search::Elasticsearch::Client::7_0::Direct/reindex()>
+functionality provided by Elasticsearch.
=head1 METHODS
@@ -277,17 +248,15 @@ are memory constrained, you will need to take this into account.
my $es = Search::Elasticsearch->new(...);
my $scroll = $es->scroll_helper(
scroll => '1m', # optional
- scroll_in_qs => 0|1, # optional
%search_params
);
-The L<Search::Elasticsearch::Client::2_0::Direct/scroll_helper()> method loads
-L<Search::Elasticsearch::Client::2_0::Scroll> class and calls L</new()>,
+The L<Search::Elasticsearch::Client::7_0::Direct/scroll_helper()> method loads
+L<Search::Elasticsearch::Client::7_0::Scroll> class and calls L</new()>,
passing in any arguments.
-You can specify a C<scroll> duration (which defaults to C<"1m">) and
-C<scroll_in_qs> (which defaults to C<false>). Any other parameters are
-passed directly to L<Search::Elasticsearch::Client::2_0::Direct/search()>.
+You can specify a C<scroll> duration (which defaults to C<"1m">).
+Any other parameters are passed directly to L<Search::Elasticsearch::Client::7_0::Direct/search()>.
The C<scroll> duration tells Elasticearch how long it should keep the scroll
alive. B<Note>: this duration doesn't need to be long enough to process
@@ -296,10 +265,7 @@ The expiry gets renewed for another C<scroll> period every time new
a new batch of results is retrieved from the cluster.
By default, the C<scroll_id> is passed as the C<body> to the
-L<scroll|Search::Elasticsearch::Client::2_0::Direct/scroll()> request.
-To send it in the query string instead, set C<scroll_in_qs> to a true value,
-but be aware: when querying very many indices, the scroll ID can become
-too long for intervening proxies.
+L<scroll|Search::Elasticsearch::Client::7_0::Direct/scroll()> request.
The C<scroll> request uses C<GET> by default. To use C<POST> instead,
set L<send_get_body_as|Search::Elasticsearch::Transport/send_get_body_as> to
@@ -342,7 +308,7 @@ the buffer.
The C<finish()> method clears out the buffer, sets L</is_finished()> to C<true>
and tries to clear the C<scroll_id> on Elasticsearch. This API is only
-supported since v0.90.5, but the call to C<clear_scroll> is wrapped in an
+supported since v0.90.6, but the call to C<clear_scroll> is wrapped in an
C<eval> so the C<finish()> method can be safely called with any version
of Elasticsearch.
@@ -393,29 +359,10 @@ How long the original search plus all subsequent batches took, in milliseconds.
=over
-=item * L<Search::Elasticsearch::Client::2_0::Bulk/reindex()>
+=item * L<Search::Elasticsearch::Client::7_0::Direct/search()>
-=item * L<Search::Elasticsearch::Client::2_0::Direct/search()>
+=item * L<Search::Elasticsearch::Client::7_0::Direct/scroll()>
-=item * L<Search::Elasticsearch::Client::2_0::Direct/scroll()>
+=item * L<Search::Elasticsearch::Client::7_0::Direct/reindex()>
=back
-
-=head1 AUTHOR
-
-Enrico Zimuel <enrico.zimuel@elastic.co>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is Copyright (c) 2020 by Elasticsearch BV.
-
-This is free software, licensed under:
-
- The Apache License, Version 2.0, January 2004
-
-=cut
-
-__END__
-
-# ABSTRACT: A helper module for scrolled searches
-
diff --git a/lib/Search/Elasticsearch/Client/7_0/TestServer.pm b/lib/Search/Elasticsearch/Client/7_0/TestServer.pm
new file mode 100644
index 0000000..fc54259
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/7_0/TestServer.pm
@@ -0,0 +1,47 @@
+# 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 Search::Elasticsearch::Client::7_0::TestServer;
+
+use strict;
+use warnings;
+
+#===================================
+sub command_line {
+#===================================
+ my ( $class, $ts, $pid_file, $dir, $transport, $http ) = @_;
+
+ return (
+ $ts->es_home . '/bin/elasticsearch',
+ '-p',
+ $pid_file->filename,
+ map {"-E$_"} (
+ 'path.data=' . $dir,
+ 'network.host=127.0.0.1',
+ 'cluster.name=es_test',
+ 'discovery.zen.ping_timeout=1s',
+ 'discovery.zen.ping.unicast.hosts=127.0.0.1:' . $ts->es_port,
+ 'transport.tcp.port=' . $transport,
+ 'http.port=' . $http,
+ @{ $ts->conf }
+ )
+ );
+}
+
+1
+
+# ABSTRACT: Client-specific backend for Search::Elasticsearch::TestServer
diff --git a/lib/Search/Elasticsearch/Client/8_0.pm b/lib/Search/Elasticsearch/Client/8_0.pm
new file mode 100644
index 0000000..ae53ece
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0.pm
@@ -0,0 +1,76 @@
+# 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 Search::Elasticsearch::Client::8_0;
+
+our $VERSION='8.00';
+use Search::Elasticsearch 8.00 ();
+
+1;
+
+__END__
+
+# ABSTRACT: Thin client with full support for Elasticsearch 8.x APIs
+
+=head1 DESCRIPTION
+
+The L<Search::Elasticsearch::Client::8_0> package provides a client
+compatible with Elasticsearch 8.x. It should be used in conjunction
+with L<Search::Elasticsearch> as follows:
+
+ $e = Search::Elasticsearch->new(
+ client => "8_0::Direct"
+ );
+
+See L<Search::Elasticsearch::Client::8_0::Direct> for documentation
+about how to use the client itself.
+
+=head1 PREVIOUS VERSIONS OF ELASTICSEARCH
+
+This version of the client supports the Elasticsearch 8.0 branch,
+which is not backwards compatible with earlier branches.
+
+If you need to talk to a version of Elasticsearch before 8.0.0, please
+install one of the following packages:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Client::7_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::6_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::5_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::2_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::1_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::0_90>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/8_0/Async.pm b/lib/Search/Elasticsearch/Client/8_0/Async.pm
new file mode 100644
index 0000000..5bdf99d
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Async.pm
@@ -0,0 +1,76 @@
+# 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 Search::Elasticsearch::Client::8_0::Async;
+
+our $VERSION='8.00';
+use Search::Elasticsearch::Client::8_0 8.00 ();
+
+1;
+
+__END__
+
+# ABSTRACT: Thin async client with full support for Elasticsearch 8.x APIs
+
+=head1 DESCRIPTION
+
+The L<Search::Elasticsearch::Client::8_0::Async> package provides a client
+compatible with Elasticsearch 8.x. It should be used in conjunction
+with L<Search::Elasticsearch::Async> as follows:
+
+ $e = Search::Elasticsearch::Async->new(
+ client => "8_0::Direct"
+ );
+
+See L<Search::Elasticsearch::Client::8_0::Direct> for documentation
+about how to use the client itself.
+
+=head1 PREVIOUS VERSIONS OF ELASTICSEARCH
+
+This version of the client supports the Elasticsearch 7.0 branch,
+which is not backwards compatible with earlier branches.
+
+If you need to talk to a version of Elasticsearch before 7.0.0, please
+install one of the following packages:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Client::7_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::6_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::5_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::2_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::1_0::Async>
+
+=item *
+
+L<Search::Elasticsearch::Client::0_90::Async>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/8_0/Async/Bulk.pm b/lib/Search/Elasticsearch/Client/8_0/Async/Bulk.pm
new file mode 100644
index 0000000..1f42cee
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Async/Bulk.pm
@@ -0,0 +1,498 @@
+# 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 Search::Elasticsearch::Client::8_0::Async::Bulk;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::Bulk',
+ 'Search::Elasticsearch::Role::Is_Async';
+
+use Search::Elasticsearch::Util qw(parse_params throw);
+use Scalar::Util qw(weaken blessed);
+use Promises qw(deferred);
+use Try::Tiny;
+use namespace::clean;
+
+has 'on_fatal' => ( is => 'lazy' );
+
+#===================================
+sub _build_on_fatal {
+#===================================
+ my $self = shift;
+ return sub {
+ warn("Fatal bulk error: @_");
+ };
+}
+
+#===================================
+sub add_action {
+#===================================
+ my $self = shift;
+ my $buffer = $self->_buffer;
+ my $max_size = $self->max_size;
+ my $max_count = $self->max_count;
+ my $max_time = $self->max_time;
+
+ my $deferred = deferred;
+ my @actions = @_;
+
+ my $weak_add;
+ my $add = sub {
+ while (@actions) {
+ my @json = try {
+ $self->_encode_action( splice( @actions, 0, 2 ) );
+ }
+ catch {
+ $self->on_fatal->($_);
+ $deferred->reject($_);
+ ();
+ };
+ return unless @json;
+
+ push @$buffer, @json;
+
+ my $size = $self->_buffer_size;
+ $size += length($_) + 1 for @json;
+ $self->_buffer_size($size);
+
+ my $count = $self->_buffer_count( $self->_buffer_count + 1 );
+
+ next
+ unless ( $max_size and $size >= $max_size )
+ || ( $max_count and $count >= $max_count )
+ || ( $max_time and time >= $self->_last_flush + $max_time );
+
+ return $self->flush->done( $weak_add,
+ sub { $deferred->reject(@_) } );
+ }
+ return $deferred->resolve;
+
+ };
+
+ weaken( $weak_add = $add );
+ $add->();
+ return $deferred->promise;
+
+}
+
+#===================================
+sub flush {
+#===================================
+ my $self = shift;
+
+ my $size = $self->_buffer_size;
+ my $count = $self->_buffer_count;
+
+ $self->_last_flush(time);
+
+ unless ($size) {
+ return deferred->resolve( { items => [] } )->promise;
+ }
+
+ my @items = ( @{ $self->_buffer } );
+ $self->clear_buffer;
+
+ if ( $self->verbose ) {
+ local $| = 1;
+ print ".";
+ }
+
+ my $promise
+ = $self->es->bulk( %{ $self->_bulk_args }, body => \@items )->catch(
+ sub {
+ my $error = shift;
+ if ( $error->is( 'Cxn', 'NoNodes' ) ) {
+ push @{ $self->_buffer }, @items;
+ $self->_buffer_size( $self->_buffer_size + $size );
+ $self->_buffer_count( $self->_buffer_count + $count );
+ }
+ die $error;
+ }
+ );
+ $promise->then( sub { $self->_report( \@items, @_ ) },
+ sub { $self->on_fatal(@_) } );
+ return $promise;
+}
+
+1;
+
+# ABSTRACT: A helper module for the Bulk API
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new;
+ my $bulk = $es->bulk_helper(
+ index => 'my_index',
+ type => 'my_type'
+ );
+
+ # Index docs:
+ $promise = $bulk->index({ id => 1, source => { foo => 'bar' }});
+ $promise = $bulk->add_action( index => { id => 1, source => { foo=> 'bar' }});
+
+ # Create docs:
+ $promise = $bulk->create({ id => 1, source => { foo => 'bar' }});
+ $promise = $bulk->add_action( create => { id => 1, source => { foo=> 'bar' }});
+ $promise = $bulk->create_docs({ foo => 'bar' })
+
+ # Delete docs:
+ $promise = $bulk->delete({ id => 1});
+ $promise = $bulk->add_action( delete => { id => 1 });
+ $promise = $bulk->delete_ids(1,2,3)
+
+ # Update docs:
+ $promise = $bulk->update({ id => 1, script => '...' });
+ $promise = $bulk->add_action( update => { id => 1, script => '...' });
+
+ # Manual flush
+ $promise = $bulk->flush;
+
+=head1 DESCRIPTION
+
+This module provides an async wrapper for the L<Search::Elasticsearch::Client::8_0::Direct/bulk()>
+method which makes it easier to run multiple create, index, update or delete
+actions in a single request.
+
+The L<Search::Elasticsearch::Client::8_0::Async::Bulk> module acts as a queue, buffering up actions
+until it reaches a maximum count of actions, or a maximum size of JSON request
+body, at which point it issues a C<bulk()> request.
+
+Once you have finished adding actions, call L</flush()> to force the final
+C<bulk()> request on the items left in the queue.
+
+This class does L<Search::Elasticsearch::Client::8_0::Role::Bulk> and
+L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 CREATING A NEW INSTANCE
+
+=head2 C<new()>
+
+ $bulk = $es->bulk_helper(
+
+ index => 'default_index', # optional
+ type => 'default_type', # optional
+ %other_bulk_params # optional
+
+ max_count => 1_000, # optional
+ max_size => 1_000_000, # optional
+ max_time => 6, # optional
+
+ verbose => 0 | 1, # optional
+
+ on_success => sub {...}, # optional
+ on_error => sub {...}, # optional
+ on_conflict => sub {...}, # optional
+ on_fatal => sub {...}, # optional
+
+ );
+
+The C<bulk_helper> method loads L<Search::Elasticsearch::Client::8_0::Async::Bulk>,
+calls L</new()> with the specified parameters and returns a new C<$bulk> object.
+
+The C<index> and C<type> parameters provide default values for
+C<index> and C<type>, which can be overridden in each action.
+You can also pass any other values which are accepted
+by the L<bulk()|Search::Elasticsearch::Client::8_0::Direct/bulk()> method.
+
+See L</flush()> for more information about the other parameters.
+
+=head1 FLUSHING THE BUFFER
+
+=head2 C<flush()>
+
+ $promise = $bulk->flush;
+
+The C<flush()> method sends all buffered actions to Elasticsearch using
+a L<bulk()|Search::Elasticsearch::Client::8_0::Direct/bulk()> request and returns
+a L<Promise>, which is rejected if the bulk request fails or if any of
+the C<on_success>, C<on_error> or C<on_conflict> callbacks throws an
+exception, otherwise it is resolved with the items that have been flushed.
+
+=head2 Auto-flushing
+
+An automatic L</flush()> is triggered whenever the C<max_count>, C<max_size>,
+or C<max_time> threshold is breached. This causes all actions in the buffer to be
+sent to Elasticsearch.
+
+=over
+
+=item * C<max_count>
+
+The maximum number of actions to allow before triggering a L</flush()>.
+This can be disabled by setting C<max_count> to C<0>. Defaults to
+C<1,000>.
+
+=item * C<max_size>
+
+The maximum size of JSON request body to allow before triggering a
+L</flush()>. This can be disabled by setting C<max_size> to C<0>. Defaults
+to C<1_000,000> bytes.
+
+=item * C<max_time>
+
+The maximum number of seconds to wait before triggering a flush. Defaults
+to C<0> seconds, which means that it is disabled. B<Note:> This timeout
+is only triggered when new items are added to the queue, not in the background.
+
+=back
+
+=head2 Errors when flushing
+
+There are two levels of error which can be thrown when L</flush()>
+is called, either manually or automatically.
+
+=over
+
+=item * Temporary Elasticsearch errors
+
+A C<Cxn> error like a C<NoNodes> error which indicates that your cluster is down.
+These errors do not clear the buffer, as they can be retried later on.
+These errors are reported via the C<on_fatal> callback and by rejecting
+the promise returned by L</flush()>, L</index()> etc.
+
+=item * Action errors
+
+Individual actions may fail. For instance, a C<create> action will fail
+if a document with the same C<index>, C<type> and C<id> already exists.
+These action errors are reported via L<callbacks|/Using callbacks>.
+
+=back
+
+=head2 Using callbacks
+
+By default, any I<Action errors> (see above) cause warnings to be
+written to C<STDERR>. However, you can use the C<on_error>, C<on_conflict>
+and C<on_success> callbacks for more fine-grained control.
+
+All callbacks receive the following arguments:
+
+=over
+
+=item C<$action>
+
+The name of the action, ie C<index>, C<create>, C<update> or C<delete>.
+
+=item C<$response>
+
+The response that Elasticsearch returned for this action.
+
+=item C<$i>
+
+The index of the action, ie the first action in the flush request
+will have C<$i> set to C<0>, the second will have C<$i> set to C<1> etc.
+
+=back
+
+=head3 C<on_success>
+
+ $bulk = $e->bulk_helper->new(
+ on_success => sub {
+ my ($action,$response,$i) = @_;
+ # do something
+ },
+ );
+
+The C<on_success> callback is called for every action that has a successful
+response.
+
+=head3 C<on_conflict>
+
+ $bulk = $e->bulk_helper->new(
+ on_conflict => sub {
+ my ($action,$response,$i,$version) = @_;
+ # do something
+ },
+ );
+
+The C<on_conflict> callback is called for actions that have triggered
+a C<Conflict> error, eg trying to C<create> a document which already
+exists. The C<$version> argument will contain the version number
+of the document currently stored in Elasticsearch (if found).
+
+=head3 C<on_error>
+
+ $bulk = $e->bulk_helper->new(
+ on_error => sub {
+ my ($action,$response,$i) = @_;
+ # do something
+ },
+ );
+
+The C<on_error> callback is called for any error (unless the C<on_conflict>)
+callback has already been called).
+
+=head2 Disabling callbacks and autoflush
+
+If you want to be in control of flushing, and you just want to receive
+the raw response that Elasticsearch sends instead of using callbacks,
+then you can do so as follows:
+
+ $bulk = $e->bulk_helper->new(
+ max_count => 0,
+ max_size => 0,
+ on_error => undef
+ );
+
+ $bulk->add_actions(....);
+ $bulk->flush
+ ->then(
+ sub { my $response = shift; ...},
+ sub { my $error = shift; ....}
+ )
+
+=head1 CREATE, INDEX, UPDATE, DELETE
+
+The L</add_action()>, L</create()>, L</create_docs()>, L</index()>,
+L</delete()>, L</delete_ids()> and L</update()> methods all return a Promise,
+which is resolved once the actions have been added to the queue and
+AFTER the queue has been flushed (if necessary). It is important
+to wait for the promise to be resolved before continuing to queue more
+items, otherwise the pending requests may fill up your available memory.
+
+For instance:
+
+ use Promises qw(deferred);
+ use Scalar::Util qw(weaken);
+
+ $bulk = $es->bulk_helper;
+
+ sub bulk_index {
+ my $d = deferred;
+ my $weak_cb;
+ my $cb = sub {
+ my @docs = get_next_docs_from_somewhere();
+ unless (@docs) {
+ return $d->resolve;
+ }
+ $bulk->index(@docs)
+ ->then(
+ $weak_cb,
+ sub { $d->reject(@_) }
+ );
+ };
+ weaken ($weak_cb = $cb);
+ $cb->();
+ $d->promise->then( sub {$b->flush} );
+ }
+
+=head2 C<add_action()>
+
+ $promise = $bulk->add_action(
+ create => { ...params... },
+ index => { ...params... },
+ update => { ...params... },
+ delete => { ...params... }
+ );
+
+The C<add_action()> method allows you to add multiple C<create>, C<index>,
+C<update> and C<delete> actions to the queue. The first value is the action
+type, and the second value is the parameters that describe that action.
+See the individual helper methods below for details.
+
+B<Note:> Parameters like C<index> or C<type> can be specified as C<index> or as
+C<_index>, so the following two lines are equivalent:
+
+ index => { index => 'index', type => 'type', id => 1, source => {...}},
+ index => { _index => 'index', _type => 'type', _id => 1, source => {...}},
+
+B<Note:> The C<index> and C<type> parameters can be specified in the
+params for any action, but if not specified, will default to the C<index>
+and C<type> values specified in L</new()>. These are required parameters:
+they must be specified either in L</new()> or in every action.
+
+=head2 C<create()>
+
+ $promise = $bulk->create(
+ { index => 'custom_index', source => { doc body }},
+ { type => 'custom_type', id => 1, source => { doc body }},
+ ...
+ );
+
+The C<create()> helper method allows you to add multiple C<create> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::8_0::Direct/create()>
+except that the document body should be passed as the C<source> or C<_source>
+parameter, instead of as C<body>.
+
+=head2 C<create_docs()>
+
+ $promise = $bulk->create_docs(
+ { doc body },
+ { doc body },
+ ...
+ );
+
+The C<create_docs()> helper is a shorter form of L</create()> which can be used
+when you are using the default C<index> and C<type> as set in L</new()>
+and you are not specifying a custom C<id> per document. In this case,
+you can just pass the individual document bodies.
+
+=head2 C<index()>
+
+ $promise = $bulk->index(
+ { index => 'custom_index', source => { doc body }},
+ { type => 'custom_type', id => 1, source => { doc body }},
+ ...
+ );
+
+The C<index()> helper method allows you to add multiple C<index> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::8_0::Direct/index()>
+except that the document body should be passed as the C<source> or C<_source>
+parameter, instead of as C<body>.
+
+=head2 C<delete()>
+
+ $promise = $bulk->delete(
+ { index => 'custom_index', id => 1},
+ { type => 'custom_type', id => 2},
+ ...
+ );
+
+The C<delete()> helper method allows you to add multiple C<delete> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::8_0::Direct/delete()>.
+
+=head2 C<delete_ids()>
+
+ $bulk->delete_ids(1,2,3...)
+
+The C<delete_ids()> helper method can be used when all of the documents you
+want to delete have the default C<index> and C<type> as set in L</new()>.
+In this case, all you have to do is to pass in a list of IDs.
+
+=head2 C<update()>
+
+ $promise = $bulk->update(
+ { id => 1,
+ doc => { partial doc },
+ doc_as_upsert => 1
+ },
+ { id => 2,
+ script => { script },
+ upsert => { upsert doc }
+ },
+ ...
+ );
+
+
+The C<update()> helper method allows you to add multiple C<update> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::8_0::Direct/update()>.
+An update can either use a I<partial doc> which gets merged with an existing
+doc (example 1 above), or can use a C<script> to update an existing doc
+(example 2 above). More information on C<script> can be found here:
+L<Search::Elasticsearch::Client::8_0::Direct/update()>.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Async/Scroll.pm b/lib/Search/Elasticsearch/Client/8_0/Async/Scroll.pm
new file mode 100644
index 0000000..c5bcee3
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Async/Scroll.pm
@@ -0,0 +1,528 @@
+# 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 Search::Elasticsearch::Client::8_0::Async::Scroll;
+
+use Moo;
+use Search::Elasticsearch::Util qw(parse_params throw);
+use Search::Elasticsearch::Async::Util qw(thenable);
+use Scalar::Util qw(weaken blessed);
+use Promises qw(deferred);
+use namespace::clean;
+
+has 'one_at_a_time' => ( is => 'ro' );
+has 'on_start' => ( is => 'ro', clearer => '_clear_on_start' );
+has 'on_results' => ( is => 'ro', clearer => '_clear_on_results' );
+has 'on_error' => ( is => 'lazy', clearer => '_clear_on_error' );
+has '_guard' => ( is => 'rwp', clearer => '_clear__guard' );
+
+with 'Search::Elasticsearch::Role::Is_Async',
+ 'Search::Elasticsearch::Client::8_0::Role::Scroll';
+
+#===================================
+sub BUILDARGS {
+#===================================
+ my ( $class, $search_params ) = parse_params(@_);
+
+ my %params;
+ for (qw(es on_start on_result on_results on_error)) {
+ my $val = delete $search_params->{$_};
+ next unless defined $val;
+ $params{$_} = $val;
+ }
+
+ $params{scroll} = $search_params->{scroll} ||= '1m';
+ $params{search_params} = $search_params;
+
+ if ( $params{on_result} ) {
+ $params{on_results} = delete $params{on_result};
+ $params{one_at_a_time} = 1;
+ }
+ elsif ( !$params{on_results} ) {
+ throw( 'Param', 'Missing required param: on_results or on_result' );
+ }
+ return \%params;
+}
+
+#===================================
+sub _build_on_error {
+#===================================
+ sub { warn "Scroll error: @_"; die @_ }
+}
+
+#===================================
+sub start {
+#===================================
+ my $self = shift;
+ $self->_set__guard($self);
+
+ $self->es->search( $self->search_params )->then(
+ sub {
+ $self->_first_results(@_);
+ }
+ )->then(
+ sub {
+ $self->_fetch_loop;
+ }
+ )->catch(
+ sub {
+ $self->on_error->(@_);
+ @_;
+ }
+ )->finally(
+ sub {
+ $self->finish;
+ $self->_clear__guard;
+ }
+ );
+}
+
+#===================================
+sub _first_results {
+#===================================
+ my ( $self, $results ) = @_;
+
+ my $total = $results->{hits}{total};
+ if (ref $total) {
+ $total = $total->{value};
+ }
+ $self->_set_total($total);
+ $self->_set_max_score( $results->{hits}{max_score} );
+ $self->_set_aggregations( $results->{aggregations} );
+ $self->_set_facets( $results->{facets} );
+ $self->_set_suggest( $results->{suggest} );
+ $self->_set_took( $results->{took} );
+ $self->_set_total_took( $results->{took} );
+
+ if ($total) {
+ $self->_set__scroll_id( $results->{_scroll_id} );
+ }
+ else {
+ $self->finish;
+ }
+
+ $self->on_start && $self->on_start->($self);
+
+ my $hits = $results->{hits}{hits};
+ return unless @$hits;
+ return $self->_push_results($hits);
+}
+
+#===================================
+sub _next_results {
+#===================================
+ my ( $self, $results ) = @_;
+ $self->_set__scroll_id( $results->{_scroll_id} );
+ $self->_set_total_took( $self->total_took + $results->{took} );
+
+ my $hits = $results->{hits}{hits};
+ return $self->finish
+ unless @$hits;
+ $self->_push_results($hits);
+}
+
+#===================================
+sub _fetch_loop {
+#===================================
+ my $self = shift;
+ my $d = deferred;
+
+ my $weak_loop;
+ my $loop = sub {
+ if ( $self->is_finished ) {
+ return $d->resolve;
+ }
+ $self->scroll_request->then( sub { $self->_next_results(@_) } )
+ ->done( $weak_loop, sub { $d->reject(@_) } );
+ };
+ weaken( $weak_loop = $loop );
+ $loop->();
+ return $d->promise;
+}
+
+#===================================
+sub _push_results {
+#===================================
+ my $self = shift;
+ my $it = $self->_results_iterator(@_);
+ my $on_results = $self->on_results;
+
+ my $deferred = deferred;
+
+ my $weak_process;
+ my $process = sub {
+ while ( !$self->is_finished ) {
+ my @results = $it->() or last;
+ my @response = $on_results->(@results);
+ my $promise = thenable(@response) or next;
+ return $promise->done( $weak_process,
+ sub { $deferred->reject(@_) } );
+ }
+ $deferred->resolve;
+ };
+ weaken( $weak_process = $process );
+ $process->();
+ return $deferred->promise;
+}
+
+#===================================
+sub _results_iterator {
+#===================================
+ my $self = shift;
+ my @results = @{ shift() };
+
+ $self->one_at_a_time
+ ? sub { splice @results, 0, 1 }
+ : sub { splice @results };
+}
+
+#===================================
+sub finish {
+#===================================
+ my $self = shift;
+ $self->_set_is_finished(1);
+
+ my $scroll_id = $self->_scroll_id;
+ $self->_clear_scroll_id;
+
+ if ( !$scroll_id || $self->_pid != $$ ) {
+ my $d = deferred;
+ $d->resolve();
+ return $d->promise;
+ }
+
+ my %args = ( body => { scroll_id => $scroll_id } );
+
+ $self->es->clear_scroll(%args)->then(
+ sub {
+ $self->_clear_on_start;
+ $self->_clear_on_results;
+ $self->_clear_on_error;
+ },
+ sub { }
+ );
+}
+
+1;
+
+# ABSTRACT: A helper module for scrolled searches
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new;
+
+ my $scroll = $es->scroll_helper
+ index => 'my_index',
+ body => {
+ size => 1000,
+ sort => '_doc',
+ query => {...}
+ },
+ on_start => \&on_start,
+ on_result => \&on_result,
+ | on_results => \&on_results,
+ on_error => \&on_error
+ );
+
+ $scroll->start->then( sub {say "Done"}, sub { warn @_ } );
+
+ sub on_start {
+ my $scroll = shift;
+ say "Total hits: ". $scroll->total;
+ }
+
+ sub on_result {
+ my $doc = shift;
+ do_something($doc);
+ }
+
+ sub on_results {
+ for my $doc (@_) {
+ do_something($doc)
+ }
+ }
+
+ sub on_error {
+ my $error = shift;
+ warn "$error";
+ }
+
+=head1 DESCRIPTION
+
+A I<scrolled search> is a search that allows you to keep pulling results
+until there are no more matching results, much like a cursor in an SQL
+database.
+
+Unlike paginating through results (with the C<from> parameter in
+L<search()|Search::Elasticsearch::Client::8_0::Direct/search()>),
+scrolled searches take a snapshot of the current state of the index. Even
+if you keep adding new documents to the index or updating existing documents,
+a scrolled search will only see the index as it was when the search began.
+
+This module is a helper utility that wraps the functionality of the
+L<search()|Search::Elasticsearch::Client::8_0::Direct/search()> and
+L<scroll()|Search::Elasticsearch::Client::8_0::Direct/scroll()> methods to make
+them easier to use.
+
+This class does L<Search::Elasticsearch::Client::8_0::Role::Scroll> and
+L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 USE CASES
+
+There are two primary use cases:
+
+=head2 Pulling enough results
+
+Perhaps you want to group your results by some field, and you don't know
+exactly how many results you will need in order to return 10 grouped
+results. With a scrolled search you can keep pulling more results
+until you have enough. For instance, you can search emails in a mailing
+list, and return results grouped by C<thread_id>:
+
+ use Promises qw(deferred);
+
+ sub find_email_threads {
+ my (%groups,@results,$scroll);
+
+ my $d = deferred;
+
+ $scroll = $es->scroll_helper(
+ index => 'my_emails',
+ type => 'email',
+ body => { query => {... some query ... }},
+ on_result => sub {
+ my $doc = shift;
+ my $thread = $doc->{_source}{thread_id};
+ unless ($groups{$thread}) {
+ $groups{$thread} = [];
+ push @results, $groups{$thread};
+ }
+ push @{$groups{$thread}},$doc;
+
+ # stop collecting if we have 10 results
+ if (@results == 10) {
+ $scroll->finish;
+ }
+ }
+ );
+
+ $scroll->start->then(
+ # resolve with results if completed successfully
+ sub { $d->resolve(@results) },
+
+ # reject with error if failed
+ sub { $d->reject(@_) }
+ );
+
+ return $d->promise;
+ }
+
+=head2 Extracting all documents
+
+Often you will want to extract all (or a subset of) documents in an index.
+If you want to change your type mappings, you will need to reindex all of your
+data. Or perhaps you want to move a subset of the data in one index into
+a new dedicated index. In these cases, you don't care about sort
+order, you just want to retrieve all documents which match a query, and do
+something with them. For instance, to retrieve all the docs for a particular
+C<client_id>:
+
+ $es->scroll_helper(
+ index => 'my_index',
+ size => 1000,
+ body => {
+ query => {
+ match => {
+ client_id => 123
+ }
+ },
+ sort => '_doc'
+ },
+ on_result => sub { do_something(@_) }
+ )->start;
+
+Very often the I<something> that you will want to do with these results
+involves bulk-indexing them into a new index. The easiest way to
+do this is to use the built-in L<Search::Elasticsearch::Client::8_0::Direct/reindex()>
+functionality provided by Elasticsearch.
+
+=head1 METHODS
+
+=head2 C<new()>
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new(...);
+ my $scroll = $es->scroll_helper(
+ scroll => '1m', # optional
+
+ on_result => sub {...} # required
+ | on_results => sub {...} # required
+
+ on_start => sub {...} # optional
+ on_error => sub {...} # optional
+ %search_params,
+ );
+ $scroll->start;
+
+The L<Search::Elasticsearch::Client::8_0::Direct/scroll_helper()> method loads
+L<Search::Elasticsearch::Client::8_0::Async::Scroll> class and calls L</new()>,
+passing in any arguments.
+
+You can specify a C<scroll> duration (which defaults to C<"1m">).
+Any other parameters are passed directly to L<Search::Elasticsearch::Client::8_0::Direct/search()>.
+
+The C<scroll> duration tells Elasticearch how long it should keep the scroll
+alive. B<Note>: this duration doesn't need to be long enough to process
+all results, just long enough to process a single B<batch> of results.
+The expiry gets renewed for another C<scroll> period every time new
+a new batch of results is retrieved from the cluster.
+
+By default, the C<scroll_id> is passed as the C<body> to the
+L<scroll|Search::Elasticsearch::Client::8_0::Direct/scroll()> request.
+
+The C<scroll> request uses C<GET> by default. To use C<POST> instead,
+set L<send_get_body_as|Search::Elasticsearch::Transport/send_get_body_as> to
+C<POST>.
+
+=head3 Callbacks
+
+You must specify either an C<on_result> callback or an C<on_results> callback.
+
+=head4 C<on_result> and C<on_results>
+
+The C<on_result> callback is called once for every result that is received.
+
+ sub on_result {
+ my $doc = shift;
+ do_something($doc);
+ }
+
+Alternatively, you can specify an C<on_results> callback which is called
+once for every set of results returned by Elasticsearch:
+
+ sub on_results {
+ for my $doc (@_) {
+ do_something($doc)
+ }
+ }
+
+If either C<on_result> or C<on_results> returns a new L<Promise>, processing
+of further results will be paused until the promise has been rejected or
+resolved.
+
+=head4 C<on_start>
+
+The C<on_start> callback is called after the first request has completed,
+at which stage the properties like C<total()>, C<aggregations()>, etc
+will have been populated.
+
+=head4 C<on_error>
+
+The C<on_error> callback is called if any error occurs. The default
+implementation warns about the error, and rethrows it.
+
+ sub on_error { warn "Scroll error: @_"; die @_ }
+
+If you wish to handle (and surpress) certain errors, then don't call C<die()>,
+eg:
+
+ sub on_error {
+ my $error = shift;
+ if ($error =~/SomeCatchableError/) {
+ # do something to handle error
+ }
+ else {
+ # rethrow error
+ die $error;
+ }
+ }
+
+=head2 C<start()>
+
+ $scroll->start
+ ->then( \&success, \&failure );
+
+The C<start()> method starts the scroll and returns a L<Promise> which
+will be resolved when the scroll completes (or L</finish()> is called),
+or rejected if any errors remain unhandled.
+
+=head2 C<finish()>
+
+ $scroll->finish;
+
+The C<finish()> method clears out the buffer, sets L</is_finished()> to C<true>
+and tries to clear the C<scroll_id> on Elasticsearch. This API is only
+supported since v0.90.6, but the call to C<clear_scroll> is wrapped in an
+C<eval> so the C<finish()> method can be safely called with any version
+of Elasticsearch.
+
+When the C<$scroll> instance goes out of scope, L</finish()> is called
+automatically if required.
+
+=head2 C<is_finished()>
+
+ $bool = $scroll->is_finished;
+
+A flag which returns C<true> if all results have been processed or
+L</finish()> has been called.
+
+=head1 INFO ACCESSORS
+
+The information from the original search is returned via the accessors
+below. These values can be accessed in the C<on_start> callback:
+
+=head2 C<total>
+
+The total number of documents that matched your query.
+
+=head2 C<max_score>
+
+The maximum score of any documents in your query.
+
+=head2 C<aggregations>
+
+Any aggregations that were specified, or C<undef>
+
+=head2 C<facets>
+
+Any facets that were specified, or C<undef>
+
+=head2 C<suggest>
+
+Any suggestions that were specified, or C<undef>
+
+=head2 C<took>
+
+How long the original search took, in milliseconds
+
+=head2 C<took_total>
+
+How long the original search plus all subsequent batches took, in milliseconds.
+This value can only be checked once the scroll has completed.
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Client::8_0::Direct/search()>
+
+=item * L<Search::Elasticsearch::Client::8_0::Direct/scroll()>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/8_0/Bulk.pm b/lib/Search/Elasticsearch/Client/8_0/Bulk.pm
new file mode 100644
index 0000000..65faabd
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Bulk.pm
@@ -0,0 +1,412 @@
+# 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 Search::Elasticsearch::Client::8_0::Bulk;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::Bulk',
+ 'Search::Elasticsearch::Role::Is_Sync';
+use Search::Elasticsearch::Util qw(parse_params throw);
+use Try::Tiny;
+use namespace::clean;
+
+#===================================
+sub add_action {
+#===================================
+ my $self = shift;
+ my $buffer = $self->_buffer;
+ my $max_size = $self->max_size;
+ my $max_count = $self->max_count;
+ my $max_time = $self->max_time;
+
+ while (@_) {
+ my @json = $self->_encode_action( splice( @_, 0, 2 ) );
+
+ push @$buffer, @json;
+
+ my $size = $self->_buffer_size;
+ $size += length($_) + 1 for @json;
+ $self->_buffer_size($size);
+
+ my $count = $self->_buffer_count( $self->_buffer_count + 1 );
+
+ $self->flush
+ if ( $max_size and $size >= $max_size )
+ || ( $max_count and $count >= $max_count )
+ || ( $max_time and time >= $self->_last_flush + $max_time );
+ }
+ return 1;
+}
+
+#===================================
+sub flush {
+#===================================
+ my $self = shift;
+ $self->_last_flush(time);
+
+ return { items => [] }
+ unless $self->_buffer_size;
+
+ if ( $self->verbose ) {
+ local $| = 1;
+ print ".";
+ }
+ my $buffer = $self->_buffer;
+ my $results = try {
+ my $res = $self->es->bulk( %{ $self->_bulk_args }, body => $buffer );
+ $self->clear_buffer;
+ return $res;
+ }
+ catch {
+ my $error = $_;
+ $self->clear_buffer
+ unless $error->is( 'Cxn', 'NoNodes' );
+
+ die $error;
+ };
+ $self->_report( $buffer, $results );
+ return defined wantarray ? $results : undef;
+}
+
+1;
+
+__END__
+
+# ABSTRACT: A helper module for the Bulk API
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch;
+
+ my $es = Search::Elasticsearch->new;
+ my $bulk = $es->bulk_helper(
+ index => 'my_index',
+ type => 'my_type'
+ );
+
+ # Index docs:
+ $bulk->index({ id => 1, source => { foo => 'bar' }});
+ $bulk->add_action( index => { id => 1, source => { foo=> 'bar' }});
+
+ # Create docs:
+ $bulk->create({ id => 1, source => { foo => 'bar' }});
+ $bulk->add_action( create => { id => 1, source => { foo=> 'bar' }});
+ $bulk->create_docs({ foo => 'bar' })
+
+ # Delete docs:
+ $bulk->delete({ id => 1});
+ $bulk->add_action( delete => { id => 1 });
+ $bulk->delete_ids(1,2,3)
+
+ # Update docs:
+ $bulk->update({ id => 1, script => '...' });
+ $bulk->add_action( update => { id => 1, script => '...' });
+
+ # Manual flush
+ $bulk->flush;
+
+=head1 DESCRIPTION
+
+This module provides a wrapper for the L<Search::Elasticsearch::Client::8_0::Direct/bulk()>
+method which makes it easier to run multiple create, index, update or delete
+actions in a single request.
+
+The L<Search::Elasticsearch::Client::8_0::Bulk> module acts as a queue, buffering up actions
+until it reaches a maximum count of actions, or a maximum size of JSON request
+body, at which point it issues a C<bulk()> request.
+
+Once you have finished adding actions, call L</flush()> to force the final
+C<bulk()> request on the items left in the queue.
+
+This class does L<Search::Elasticsearch::Client::8_0::Role::Bulk> and
+L<Search::Elasticsearch::Role::Is_Sync>.
+
+=head1 CREATING A NEW INSTANCE
+
+=head2 C<new()>
+
+ my $bulk = $es->bulk_helper(
+
+ index => 'default_index', # optional
+ type => 'default_type', # optional
+ %other_bulk_params # optional
+
+ max_count => 1_000, # optional
+ max_size => 1_000_000, # optional
+ max_time => 6, # optional
+
+ verbose => 0 | 1, # optional
+
+ on_success => sub {...}, # optional
+ on_error => sub {...}, # optional
+ on_conflict => sub {...}, # optional
+
+
+ );
+
+The C<new()> method returns a new C<$bulk> object. You must pass your
+Search::Elasticsearch client as the C<es> argument.
+
+The C<index> and C<type> parameters provide default values for
+C<index> and C<type>, which can be overridden in each action.
+You can also pass any other values which are accepted
+by the L<bulk()|Search::Elasticsearch::Client::8_0::Direct/bulk()> method.
+
+See L</flush()> for more information about the other parameters.
+
+=head1 FLUSHING THE BUFFER
+
+=head2 C<flush()>
+
+ $result = $bulk->flush;
+
+The C<flush()> method sends all buffered actions to Elasticsearch using
+a L<bulk()|Search::Elasticsearch::Client::8_0::Direct/bulk()> request.
+
+=head2 Auto-flushing
+
+An automatic L</flush()> is triggered whenever the C<max_count>, C<max_size>,
+or C<max_time> threshold is breached. This causes all actions in the buffer to be
+sent to Elasticsearch.
+
+=over
+
+=item * C<max_count>
+
+The maximum number of actions to allow before triggering a L</flush()>.
+This can be disabled by setting C<max_count> to C<0>. Defaults to
+C<1,000>.
+
+=item * C<max_size>
+
+The maximum size of JSON request body to allow before triggering a
+L</flush()>. This can be disabled by setting C<max_size> to C<0>. Defaults
+to C<1_000,000> bytes.
+
+=item * C<max_time>
+
+The maximum number of seconds to wait before triggering a flush. Defaults
+to C<0> seconds, which means that it is disabled. B<Note:> This timeout
+is only triggered when new items are added to the queue, not in the background.
+
+=back
+
+=head2 Errors when flushing
+
+There are two types of error which can be thrown when L</flush()>
+is called, either manually or automatically.
+
+=over
+
+=item * Temporary Elasticsearch errors
+
+A C<Cxn> error like a C<NoNodes> error which indicates that your cluster is down.
+These errors do not clear the buffer, as they can be retried later on.
+
+=item * Action errors
+
+Individual actions may fail. For instance, a C<create> action will fail
+if a document with the same C<index>, C<type> and C<id> already exists.
+These action errors are reported via L<callbacks|/Using callbacks>.
+
+=back
+
+=head2 Using callbacks
+
+By default, any I<Action errors> (see above) cause warnings to be
+written to C<STDERR>. However, you can use the C<on_error>, C<on_conflict>
+and C<on_success> callbacks for more fine-grained control.
+
+All callbacks receive the following arguments:
+
+=over
+
+=item C<$action>
+
+The name of the action, ie C<index>, C<create>, C<update> or C<delete>.
+
+=item C<$response>
+
+The response that Elasticsearch returned for this action.
+
+=item C<$i>
+
+The index of the action, ie the first action in the flush request
+will have C<$i> set to C<0>, the second will have C<$i> set to C<1> etc.
+
+=back
+
+=head3 C<on_success>
+
+ my $bulk = $es->bulk_helper(
+ on_success => sub {
+ my ($action,$response,$i) = @_;
+ # do something
+ },
+ );
+
+The C<on_success> callback is called for every action that has a successful
+response.
+
+=head3 C<on_conflict>
+
+ my $bulk = $es->bulk_helper(
+ on_conflict => sub {
+ my ($action,$response,$i,$version) = @_;
+ # do something
+ },
+ );
+
+The C<on_conflict> callback is called for actions that have triggered
+a C<Conflict> error, eg trying to C<create> a document which already
+exists. The C<$version> argument will contain the version number
+of the document currently stored in Elasticsearch (if found).
+
+=head3 C<on_error>
+
+ my $bulk = $es->bulk_helper(
+ on_error => sub {
+ my ($action,$response,$i) = @_;
+ # do something
+ },
+ );
+
+The C<on_error> callback is called for any error (unless the C<on_conflict>)
+callback has already been called).
+
+=head2 Disabling callbacks and autoflush
+
+If you want to be in control of flushing, and you just want to receive
+the raw response that Elasticsearch sends instead of using callbacks,
+then you can do so as follows:
+
+ my $bulk = $es->bulk_helper(
+ max_count => 0,
+ max_size => 0,
+ on_error => undef
+ );
+
+ $bulk->add_actions(....);
+ $response = $bulk->flush;
+
+=head1 CREATE, INDEX, UPDATE, DELETE
+
+=head2 C<add_action()>
+
+ $bulk->add_action(
+ create => { ...params... },
+ index => { ...params... },
+ update => { ...params... },
+ delete => { ...params... }
+ );
+
+The C<add_action()> method allows you to add multiple C<create>, C<index>,
+C<update> and C<delete> actions to the queue. The first value is the action
+type, and the second value is the parameters that describe that action.
+See the individual helper methods below for details.
+
+B<Note:> Parameters like C<index> or C<type> can be specified as C<index> or as
+C<_index>, so the following two lines are equivalent:
+
+ index => { index => 'index', type => 'type', id => 1, source => {...}},
+ index => { _index => 'index', _type => 'type', _id => 1, source => {...}},
+
+B<Note:> The C<index> and C<type> parameters can be specified in the
+params for any action, but if not specified, will default to the C<index>
+and C<type> values specified in L</new()>. These are required parameters:
+they must be specified either in L</new()> or in every action.
+
+=head2 C<create()>
+
+ $bulk->create(
+ { index => 'custom_index', source => { doc body }},
+ { type => 'custom_type', id => 1, source => { doc body }},
+ ...
+ );
+
+The C<create()> helper method allows you to add multiple C<create> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::8_0::Direct/create()>
+except that the document body should be passed as the C<source> or C<_source>
+parameter, instead of as C<body>.
+
+=head2 C<create_docs()>
+
+ $bulk->create_docs(
+ { doc body },
+ { doc body },
+ ...
+ );
+
+The C<create_docs()> helper is a shorter form of L</create()> which can be used
+when you are using the default C<index> and C<type> as set in L</new()>
+and you are not specifying a custom C<id> per document. In this case,
+you can just pass the individual document bodies.
+
+=head2 C<index()>
+
+ $bulk->index(
+ { index => 'custom_index', source => { doc body }},
+ { type => 'custom_type', id => 1, source => { doc body }},
+ ...
+ );
+
+The C<index()> helper method allows you to add multiple C<index> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::8_0::Direct/index()>
+except that the document body should be passed as the C<source> or C<_source>
+parameter, instead of as C<body>.
+
+=head2 C<delete()>
+
+ $bulk->delete(
+ { index => 'custom_index', id => 1},
+ { type => 'custom_type', id => 2},
+ ...
+ );
+
+The C<delete()> helper method allows you to add multiple C<delete> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::8_0::Direct/delete()>.
+
+=head2 C<delete_ids()>
+
+ $bulk->delete_ids(1,2,3...)
+
+The C<delete_ids()> helper method can be used when all of the documents you
+want to delete have the default C<index> and C<type> as set in L</new()>.
+In this case, all you have to do is to pass in a list of IDs.
+
+=head2 C<update()>
+
+ $bulk->update(
+ { id => 1,
+ doc => { partial doc },
+ doc_as_upsert => 1
+ },
+ { id => 2,
+ script => { script }
+ upsert => { upsert doc }
+ },
+ ...
+ );
+
+
+The C<update()> helper method allows you to add multiple C<update> actions.
+It accepts the same parameters as L<Search::Elasticsearch::Client::8_0::Direct/update()>.
+An update can either use a I<partial doc> which gets merged with an existing
+doc (example 1 above), or can use a C<script> to update an existing doc
+(example 2 above). More information on C<script> can be found here:
+L<Search::Elasticsearch::Client::8_0::Direct/update()>.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct.pm b/lib/Search/Elasticsearch/Client/8_0/Direct.pm
new file mode 100644
index 0000000..1012ee0
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct.pm
@@ -0,0 +1,1683 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Client::8_0::Direct;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+
+use Search::Elasticsearch::Util qw(parse_params is_compat);
+use namespace::clean;
+
+sub _namespace {__PACKAGE__}
+
+has 'async_search' => ( is => 'lazy', init_arg => undef );
+has 'autoscaling' => ( is => 'lazy', init_arg => undef );
+has 'cat' => ( is => 'lazy', init_arg => undef );
+has 'ccr' => ( is => 'lazy', init_arg => undef );
+has 'cluster' => ( is => 'lazy', init_arg => undef );
+has 'dangling_indices' => ( is => 'lazy', init_arg => undef );
+has 'enrich' => ( is => 'lazy', init_arg => undef );
+has 'eql' => ( is => 'lazy', init_arg => undef );
+has 'features' => ( is => 'lazy', init_arg => undef );
+has 'fleet' => ( is => 'lazy', init_arg => undef );
+has 'graph' => ( is => 'lazy', init_arg => undef );
+has 'ilm' => ( is => 'lazy', init_arg => undef );
+has 'indices' => ( is => 'lazy', init_arg => undef );
+has 'ingest' => ( is => 'lazy', init_arg => undef );
+has 'license' => ( is => 'lazy', init_arg => undef );
+has 'logstash' => ( is => 'lazy', init_arg => undef );
+has 'migration' => ( is => 'lazy', init_arg => undef );
+has 'ml' => ( is => 'lazy', init_arg => undef );
+has 'monitoring' => ( is => 'lazy', init_arg => undef );
+has 'nodes' => ( is => 'lazy', init_arg => undef );
+has 'rollup' => ( is => 'lazy', init_arg => undef );
+has 'searchable_snapshots' => ( is => 'lazy', init_arg => undef );
+has 'security' => ( is => 'lazy', init_arg => undef );
+has 'shutdown' => ( is => 'lazy', init_arg => undef );
+has 'snapshot' => ( is => 'lazy', init_arg => undef );
+has 'slm' => ( is => 'lazy', init_arg => undef );
+has 'sql' => ( is => 'lazy', init_arg => undef );
+has 'ssl' => ( is => 'lazy', init_arg => undef );
+has 'tasks' => ( is => 'lazy', init_arg => undef );
+has 'transform' => ( is => 'lazy', init_arg => undef );
+has 'watcher' => ( is => 'lazy', init_arg => undef );
+has 'xpack' => ( is => 'lazy', init_arg => undef );
+has 'bulk_helper_class' => ( is => 'rw' );
+has 'scroll_helper_class' => ( is => 'rw' );
+has '_bulk_class' => ( is => 'lazy' );
+has '_scroll_class' => ( is => 'lazy' );
+
+#===================================
+sub _build__bulk_class {
+#===================================
+ my $self = shift;
+ my $bulk_class = $self->bulk_helper_class
+ || 'Client::' . $self->api_version . '::Bulk';
+ $self->_build_helper( 'bulk', $bulk_class );
+}
+
+#===================================
+sub _build__scroll_class {
+#===================================
+ my $self = shift;
+ my $scroll_class = $self->scroll_helper_class
+ || 'Client::' . $self->api_version . '::Scroll';
+ $self->_build_helper( 'scroll', $scroll_class );
+}
+
+#===================================
+sub bulk_helper {
+#===================================
+ my ( $self, $params ) = parse_params(@_);
+ $params->{es} ||= $self;
+ $self->_bulk_class->new($params);
+}
+
+#===================================
+sub scroll_helper {
+#===================================
+ my ( $self, $params ) = parse_params(@_);
+ $params->{es} ||= $self;
+ $self->_scroll_class->new($params);
+}
+
+#===================================
+sub _build_autoscaling { shift->_build_namespace('Autoscaling') }
+sub _build_async_search { shift->_build_namespace('AsyncSearch') }
+sub _build_cat { shift->_build_namespace('Cat') }
+sub _build_ccr { shift->_build_namespace('CCR') }
+sub _build_cluster { shift->_build_namespace('Cluster') }
+sub _build_dangling_indices { shift->_build_namespace('DanglingIndices') }
+sub _build_enrich { shift->_build_namespace('Enrich') }
+sub _build_eql { shift->_build_namespace('Eql') }
+sub _build_features { shift->_build_namespace('Features') }
+sub _build_fleet { shift->_build_namespace('Fleet') }
+sub _build_graph { shift->_build_namespace('Graph') }
+sub _build_ilm { shift->_build_namespace('ILM') }
+sub _build_indices { shift->_build_namespace('Indices') }
+sub _build_ingest { shift->_build_namespace('Ingest') }
+sub _build_license { shift->_build_namespace('License') }
+sub _build_logstash { shift->_build_namespace('Logstash') }
+sub _build_migration { shift->_build_namespace('Migration') }
+sub _build_ml { shift->_build_namespace('ML') }
+sub _build_monitoring { shift->_build_namespace('Monitoring') }
+sub _build_nodes { shift->_build_namespace('Nodes') }
+sub _build_rollup { shift->_build_namespace('Rollup') }
+sub _build_searchable_snapshots { shift->_build_namespace('SearchableSnapshots') }
+sub _build_security { shift->_build_namespace('Security') }
+sub _build_shutdown { shift->_build_namespace('Shutdown') }
+sub _build_snapshot { shift->_build_namespace('Snapshot') }
+sub _build_slm { shift->_build_namespace('Slm') }
+sub _build_sql { shift->_build_namespace('SQL') }
+sub _build_ssl { shift->_build_namespace('SSL') }
+sub _build_tasks { shift->_build_namespace('Tasks') }
+sub _build_transform { shift->_build_namespace('Transform') }
+sub _build_watcher { shift->_build_namespace('Watcher') }
+sub _build_xpack { shift->_build_namespace('XPack') }
+#===================================
+
+__PACKAGE__->_install_api('');
+
+1;
+
+__END__
+
+# ABSTRACT: Thin client with full support for Elasticsearch 8.x APIs
+
+=head1 SYNOPSIS
+
+Create a client:
+
+ use Search::Elasticsearch;
+ my $e = Search::Elasticsearch->new(
+ client => '8_0::Direct'
+ );
+
+Index a doc:
+
+ $e->index(
+ index => 'my_index',
+ type => 'blog_post',
+ id => 123,
+ body => {
+ title => "Elasticsearch clients",
+ content => "Interesting content...",
+ date => "2013-09-23"
+ }
+ );
+
+Get a doc:
+
+ $e->get(
+ index => 'my_index',
+ type => 'my_type',
+ id => 123
+ );
+
+Search for docs:
+
+ $results = $e->search(
+ index => 'my_index',
+ body => {
+ query => {
+ match => {
+ title => "elasticsearch"
+ }
+ }
+ }
+ );
+
+Index-level requests:
+
+ $e->indices->create( index => 'my_index' );
+ $e->indices->delete( index => 'my_index' )
+
+Ingest pipeline requests:
+
+ $e->ingest->get_pipeline( id => 'apache-logs' );
+
+Cluster-level requests:
+
+ $health = $e->cluster->health;
+
+Node-level requests:
+
+ $info = $e->nodes->info;
+ $stats = $e->nodes->stats;
+
+Snapshot and restore:
+
+ $e->snapshot->create_repository(
+ repository => 'my_backups',
+ type => 'fs',
+ settings => {
+ location => '/mnt/backups'
+ }
+ );
+
+ $e->snapshot->create(
+ repository => 'my_backups',
+ snapshot => 'backup_2014'
+ );
+
+Task management:
+
+ $e->tasks->list;
+
+`cat` debugging:
+
+ say $e->cat->allocation;
+ say $e->cat->health;
+
+Cross-cluster replication requests:
+
+ say $e->ccr->follow;
+
+Index lifecycle management requests:
+
+ say $e->ilm->put_lifecycle;
+
+=head1 DESCRIPTION
+
+The L<Search::Elasticsearch::Client::8_0::Direct> class provides the
+Elasticsearch 8.x compatible client returned by:
+
+ $e = Search::Elasticsearch->new(
+ client => "8_0::Direct" # default
+ );
+
+It is intended to be as close as possible to the native REST API that
+Elasticsearch uses, so that it is easy to translate the
+L<Elasticsearch reference documentation|http://www.elasticsearch/guide>
+for an API to the equivalent in this client.
+
+This class provides the methods for L<document CRUD|/DOCUMENT CRUD METHODS>,
+L<bulk document CRUD|/BULK DOCUMENT CRUD METHODS> and L<search|/SEARCH METHODS>.
+It also provides access to clients for managing L<indices|/indices()>
+and the L<cluster|/cluster()>.
+
+=head1 PREVIOUS VERSIONS OF ELASTICSEARCH
+
+This version of the client supports the Elasticsearch 8.0 branch,
+which is not backwards compatible with earlier branches.
+
+If you need to talk to a version of Elasticsearch before 8.0.0, please
+install one of the following modules:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Client::7_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::6_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::5_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::2_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::1_0>
+
+=item *
+
+L<Search::Elasticsearch::Client::0_90>
+
+=back
+
+=head1 CONVENTIONS
+
+=head2 Parameter passing
+
+Parameters can be passed to any request method as a list or as a hash
+reference. The following two statements are equivalent:
+
+ $e->search( size => 10 );
+ $e->search({size => 10});
+
+=head2 Path parameters
+
+Any values that should be included in the URL path, eg C</{index}/{type}>
+should be passed as top level parameters:
+
+ $e->search( index => 'my_index', type => 'my_type' );
+
+Alternatively, you can specify a C<path> parameter directly:
+
+ $e->search( path => '/my_index/my_type' );
+
+=head2 Query-string parameters
+
+Any values that should be included in the query string should be passed
+as top level parameters:
+
+ $e->search( size => 10 );
+
+If you pass in a C<\%params> hash, then it will be included in the
+query string parameters without any error checking. The following:
+
+ $e->search( size => 10, params => { from => 6, size => 6 })
+
+would result in this query string:
+
+ ?from=6&size=10
+
+=head2 Body parameter
+
+The request body should be passed in the C<body> key:
+
+ $e->search(
+ body => {
+ query => {...}
+ }
+ );
+
+The body can also be a UTF8-decoded string, which will be converted into
+UTF-8 bytes and passed as is:
+
+ $e->indices->analyze( body => "The quick brown fox");
+
+=head2 Boolean parameters
+
+Elasticsearch 7.0.0 and above no longer accepts truthy and falsey values for booleans. Instead,
+it will accept only a JSON C<true> or C<false>, or the string equivalents C<"true"> or C<"false">.
+
+In the Perl client, you can use the following values:
+
+=over
+
+=item * True: C<true>, C<\1>, or a L<JSON::PP::Boolean> object.
+
+=item * False: C<false>, C<\0>, or a L<JSON::PP::Boolean> object.
+
+=back
+
+=head2 Filter path parameter
+
+Any API which returns a JSON body accepts a C<filter_path> parameter
+which will filter the JSON down to only the specified paths. For instance,
+if you are running a search request and only want the C<total> hits and
+the C<_source> field for each hit (without the C<_id>, C<_index> etc),
+you can do:
+
+ $e->search(
+ query => {...},
+ filter_paths => [ 'hits.total', 'hits.hits._source' ]
+ );
+
+=head2 Ignore parameter
+
+Normally, any HTTP status code outside the 200-299 range will result in
+an error being thrown. To suppress these errors, you can specify which
+status codes to ignore in the C<ignore> parameter.
+
+ $e->indices->delete(
+ index => 'my_index',
+ ignore => 404
+ );
+
+This is most useful for
+L<Missing|Search::Elasticsearch::Error/Search::Elasticsearch::Error::Missing> errors, which
+are triggered by a C<404> status code when some requested resource does
+not exist.
+
+Multiple error codes can be specified with an array:
+
+ $e->indices->delete(
+ index => 'my_index',
+ ignore => [404,409]
+ );
+
+=head1 CONFIGURATION
+
+=head2 C<bulk_helper_class>
+
+The class to use for the L</bulk_helper()> method. Defaults to
+L<Search::Elasticsearch::Client::8_0::Bulk>.
+
+=head2 C<scroll_helper_class>
+
+The class to use for the L</scroll_helper()> method. Defaults to
+L<Search::Elasticsearch::Client::8_0::Scroll>.
+
+=head1 GENERAL METHODS
+
+=head2 C<info()>
+
+ $info = $e->info
+
+Returns information about the version of Elasticsearch that the responding node
+is running.
+
+=head2 C<ping()>
+
+ $e->ping
+
+Pings a node in the cluster and returns C<1> if it receives a C<200>
+response, otherwise it throws an error.
+
+=head2 C<indices()>
+
+ $indices_client = $e->indices;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::Indices> object which can be used
+for managing indices, eg creating, deleting indices, managing mapping,
+index settings etc.
+
+=head2 C<ingest()>
+
+ $ingest_client = $e->ingest;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::Ingest> object which can be used
+for managing ingest pipelines.
+
+=head2 C<cluster()>
+
+ $cluster_client = $e->cluster;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::Cluster> object which can be used
+for managing the cluster, eg cluster-wide settings and cluster health.
+
+=head2 C<nodes()>
+
+ $node_client = $e->nodes;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::Nodes> object which can be used
+to retrieve node info and stats.
+
+=head2 C<snapshot()>
+
+ $snapshot_client = $e->snapshot;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::Snapshot> object which
+is used for managing backup repositories and creating and restoring
+snapshots.
+
+=head2 C<tasks()>
+
+ $tasks_client = $e->tasks;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::Tasks> object which
+is used for accessing the task management API.
+
+=head2 C<cat()>
+
+ $cat_client = $e->cat;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::Cat> object which can be used
+to retrieve simple to read text info for debugging and monitoring an
+Elasticsearch cluster.
+
+=head2 C<ccr()>
+
+ $ccr_client = $e->ccr;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::CCR> object which can be used
+to handle cross-cluster replication requests.
+
+
+=head2 C<ilm()>
+
+ $ilm_client = $e->ilm;
+
+Returns a L<Search::Elasticsearch::Client::8_0::Direct::ILM> object which can be used
+to handle index lifecycle management requests.
+
+=head1 DOCUMENT CRUD METHODS
+
+These methods allow you to perform create, index, update and delete requests
+for single documents:
+
+=head2 C<index()>
+
+ $response = $e->index(
+ index => 'index_name', # required
+ type => 'type_name', # required
+ id => 'doc_id', # optional, otherwise auto-generated
+
+ body => { document } # required
+ );
+
+The C<index()> method is used to index a new document or to reindex
+an existing document.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<if_primary_term>,
+ C<if_seq_no>,
+ C<op_type>,
+ C<parent>,
+ C<pipeline>,
+ C<refresh>,
+ C<routing>,
+ C<timeout>,
+ C<version>,
+ C<version_type>,
+ C<wait_for_active_shards>
+
+See the L<index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html>
+for more information.
+
+=head2 C<create()>
+
+ $response = $e->create(
+ index => 'index_name', # required
+ type => 'type_name', # required
+ id => 'doc_id', # required
+
+ body => { document } # required
+ );
+
+The C<create()> method works exactly like the L</index()> method, except
+that it will throw a C<Conflict> error if a document with the same
+C<index>, C<type> and C<id> already exists.
+
+Query string parameters:
+ C<consistency>,
+ C<error_trace>,
+ C<human>,
+ C<op_type>,
+ C<parent>,
+ C<refresh>,
+ C<routing>,
+ C<timeout>,
+ C<version>,
+ C<version_type>
+
+See the L<create docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-create.html>
+for more information.
+
+=head2 C<get()>
+
+ $response = $e->get(
+ index => 'index_name', # required
+ type => 'type_name', # required
+ id => 'doc_id', # required
+ );
+
+The C<get()> method will retrieve the document with the specified
+C<index>, C<type> and C<id>, or will throw a C<Missing> error.
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<human>,
+ C<parent>,
+ C<preference>,
+ C<realtime>,
+ C<refresh>,
+ C<routing>,
+ C<stored_fields>,
+ C<version>,
+ C<version_type>
+
+See the L<get docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html>
+for more information.
+
+=head2 C<get_source()>
+
+ $response = $e->get_source(
+ index => 'index_name', # required
+ type => 'type_name', # required
+ id => 'doc_id', # required
+ );
+
+The C<get_source()> method works just like the L</get()> method except that
+it returns just the C<_source> field (the value of the C<body> parameter
+in the L</index()> method) instead of returning the C<_source> field
+plus the document metadata, ie the C<_index>, C<_type> etc.
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<human>,
+ C<parent>,
+ C<preference>,
+ C<realtime>,
+ C<refresh>,
+ C<routing>,
+ C<version>,
+ C<version_type>
+
+See the L<get_source docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html>
+for more information.
+
+=head2 C<exists()>
+
+ $response = $e->exists(
+ index => 'index_name', # required
+ type => 'type_name', # required
+ id => 'doc_id', # required
+ );
+
+The C<exists()> method returns C<1> if a document with the specified
+C<index>, C<type> and C<id> exists, or an empty string if it doesn't.
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<human>,
+ C<parent>,
+ C<preference>,
+ C<realtime>,
+ C<refresh>,
+ C<routing>,
+ C<version>,
+ C<version_type>
+
+See the L<exists docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html>
+for more information.
+
+=head2 C<delete()>
+
+ $response = $e->delete(
+ index => 'index_name', # required
+ type => 'type_name', # required
+ id => 'doc_id', # required
+ );
+
+The C<delete()> method will delete the document with the specified
+C<index>, C<type> and C<id>, or will throw a C<Missing> error.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<if_primary_term>,
+ C<if_seq_no>,
+ C<parent>,
+ C<refresh>,
+ C<routing>,
+ C<timeout>,
+ C<version>,
+ C<version_type>,
+ C<wait_for_active_shards>
+
+See the L<delete docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html>
+for more information.
+
+=head2 C<update()>
+
+ $response = $e->update(
+ index => 'index_name', # required
+ type => 'type_name', # required
+ id => 'doc_id', # required
+
+ body => { update } # required
+ );
+
+The C<update()> method updates a document with the corresponding
+C<index>, C<type> and C<id> if it exists. Updates can be performed either by:
+
+=over
+
+=item * providing a partial document to be merged in to the existing document:
+
+ $response = $e->update(
+ ...,
+ body => {
+ doc => { new_field => 'new_value'},
+ }
+ );
+
+=item * with an inline script:
+
+ $response = $e->update(
+ ...,
+ body => {
+ script => {
+ source => "ctx._source.counter += incr",
+ params => { incr => 6 }
+ }
+ }
+ );
+
+=item * with an indexed script:
+
+ $response = $e->update(
+ ...,
+ body => {
+ script => {
+ id => $id,
+ lang => 'painless',
+ params => { incr => 6 }
+ }
+ }
+ );
+
+See L<indexed scripts|https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_indexed_scripts>
+for more information.
+
+=item * with a script stored as a file:
+
+ $response = $e->update(
+ ...,
+ body => {
+ script => {
+ file => 'counter',
+ lang => 'painless',
+ params => { incr => 6 }
+ }
+ }
+ );
+
+See L<scripting docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html>
+for more information.
+
+=back
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<fields>,
+ C<human>,
+ C<if_primary_term>,
+ C<if_seq_no>,
+ C<lang>,
+ C<parent>,
+ C<refresh>,
+ C<retry_on_conflict>,
+ C<routing>,
+ C<timeout>,
+ C<version>,
+ C<version_type>,
+ C<wait_for_active_shards>
+
+See the L<update docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html>
+for more information.
+
+=head2 C<termvectors()>
+
+ $results = $e->termvectors(
+ index => $index, # required
+ type => $type, # required
+
+ id => $id, # optional
+ body => {...} # optional
+ )
+
+The C<termvectors()> method retrieves term and field statistics, positions,
+offsets and payloads for the specified document, assuming that termvectors
+have been enabled.
+
+Query string parameters:
+ C<error_trace>,
+ C<field_statistics>,
+ C<fields>,
+ C<human>,
+ C<offsets>,
+ C<parent>,
+ C<payloads>,
+ C<positions>,
+ C<preference>,
+ C<realtime>,
+ C<routing>,
+ C<term_statistics>,
+ C<version>,
+ C<version_type>
+
+See the L<termvector docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-termvectors.html>
+for more information.
+
+=head1 BULK DOCUMENT CRUD METHODS
+
+The bulk document CRUD methods are used for running multiple CRUD actions
+within a single request. By reducing the number of network requests
+that need to be made, bulk requests greatly improve performance.
+
+=head2 C<bulk()>
+
+ $response = $e->bulk(
+ index => 'index_name', # required if type specified
+ type => 'type_name', # optional
+
+ body => [ actions ] # required
+ );
+
+See L<Search::Elasticsearch::Client::8_0::Bulk> and L</bulk_helper()> for a helper module that makes
+bulk indexing simpler to use.
+
+The C<bulk()> method can perform multiple L</index()>, L</create()>,
+L</delete()> or L</update()> actions with a single request. The C<body>
+parameter expects an array containing the list of actions to perform.
+
+An I<action> consists of an initial metadata hash ref containing the action
+type, plus the associated metadata, eg :
+
+ { delete => { _index => 'index', _type => 'type', _id => 123 }}
+
+The C<index> and C<create> actions then expect a hashref containing
+the document itself:
+
+ { create => { _index => 'index', _type => 'type', _id => 123 }},
+ { title => "A newly created document" }
+
+And the C<update> action expects a hashref containing the update commands,
+eg:
+
+ { update => { _index => 'index', _type => 'type', _id => 123 }},
+ { script => "ctx._source.counter+=1" }
+
+
+Each action can include the same parameters that you would pass to
+the equivalent L</index()>, L</create()>, L</delete()> or L</update()>
+request, except that C<_index>, C<_type> and C<_id> must be specified with
+the preceding underscore. All other parameters can be specified with or
+without the underscore.
+
+For instance:
+
+ $response = $e->bulk(
+ index => 'index_name', # default index name
+ type => 'type_name', # default type name
+ body => [
+
+ # create action
+ { create => {
+ _index => 'not_the_default_index',
+ _type => 'not_the_default_type',
+ _id => 123
+ }},
+ { title => 'Foo' },
+
+ # index action
+ { index => { _id => 124 }},
+ { title => 'Foo' },
+
+ # delete action
+ { delete => { _id => 126 }},
+
+ # update action
+ { update => { _id => 126 }},
+ { script => "ctx._source.counter+1" }
+ ]
+ );
+
+Each action is performed separately. One failed action will not
+cause the others to fail as well.
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<fields>,
+ C<human>,
+ C<pipeline>,
+ C<refresh>,
+ C<routing>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<bulk docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html>
+for more information.
+
+=head2 C<bulk_helper()>
+
+ $bulk_helper = $e->bulk_helper( @args );
+
+Returns a new instance of the class specified in the L</bulk_helper_class>,
+which defaults to L<Search::Elasticsearch::Client::8_0::Bulk>.
+
+=head2 C<mget()>
+
+ $results = $e->mget(
+ index => 'default_index', # optional, required when type specified
+ type => 'default_type', # optional
+
+ body => { docs or ids } # required
+ );
+
+The C<mget()> method will retrieve multiple documents with a single request.
+The C<body> consists of an array of documents to retrieve:
+
+ $results = $e->mget(
+ index => 'default_index',
+ type => 'default_type',
+ body => {
+ docs => [
+ { _id => 1},
+ { _id => 2, _type => 'not_the_default_type' }
+ ]
+ }
+ );
+
+You can also pass any of the other parameters that the L</get()> request
+accepts.
+
+If you have specified an C<index> and C<type>, you can just include the
+C<ids> of the documents to retrieve:
+
+ $results = $e->mget(
+ index => 'default_index',
+ type => 'default_type',
+ body => {
+ ids => [ 1, 2, 3]
+ }
+ );
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<error_trace>,
+ C<human>,
+ C<preference>,
+ C<realtime>,
+ C<refresh>,
+ C<routing>,
+ C<stored_fields>
+
+See the L<mget docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html>
+for more information.
+
+=head2 C<mtermvectors()>
+
+ $results = $e->mtermvectors(
+ index => $index, # required if type specified
+ type => $type, # optional
+
+ body => { } # optional
+ )
+
+Runs multiple L</termvector()> requests in a single request, eg:
+
+ $results = $e->mtermvectors(
+ index => 'test',
+ body => {
+ docs => [
+ { _type => 'test', _id => 1, fields => ['text'] },
+ { _type => 'test', _id => 2, payloads => 1 },
+ ]
+ }
+ );
+
+Query string parameters:
+ C<error_trace>,
+ C<field_statistics>,
+ C<fields>,
+ C<human>,
+ C<ids>,
+ C<offsets>,
+ C<parent>,
+ C<payloads>,
+ C<positions>,
+ C<preference>,
+ C<realtime>,
+ C<routing>,
+ C<term_statistics>,
+ C<version>,
+ C<version_type>
+
+See the L<mtermvectors docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-termvectors.html>
+for more information.
+
+=head1 SEARCH METHODS
+
+The search methods are used for querying documents in one, more or all indices
+and of one, more or all types:
+
+=head2 C<search()>
+
+ $results = $e->search(
+ index => 'index' | \@indices, # optional
+ type => 'type' | \@types, # optional
+
+ body => { search params } # optional
+ );
+
+The C<search()> method searches for matching documents in one or more
+indices. It is just as easy to search a single index as it is to search
+all the indices in your cluster. It can also return
+L<aggregations|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html>
+L<highlighted snippets|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-highlighting.html>
+and L<did-you-mean|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-phrase.html>
+or L<search-as-you-type|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html>
+suggestions.
+
+The I<lite> L<version of search|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html>
+allows you to specify a query string in the C<q> parameter, using the
+Lucene query string syntax:
+
+ $results = $e->search( q => 'title:(elasticsearch clients)');
+
+However, the preferred way to search is by using the
+L<Query DSL|http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html>
+to create a query, and passing that C<query> in the
+L<request body|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html>:
+
+ $results = $e->search(
+ body => {
+ query => {
+ match => { title => 'Elasticsearch clients'}
+ }
+ }
+ );
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<allow_no_indices>,
+ C<allow_partial_search_results>,
+ C<analyze_wildcard>,
+ C<analyzer>,
+ C<batched_reduce_size>,
+ C<default_operator>,
+ C<df>,
+ C<docvalue_fields>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<explain>,
+ C<from>,
+ C<human>,
+ C<ignore_throttled>,
+ C<ignore_unavailable>,
+ C<lenient>,
+ C<max_concurrent_shard_requests>,
+ C<pre_filter_shard_size>,
+ C<preference>,
+ C<q>,
+ C<request_cache>,
+ C<rest_total_hits_as_int>,
+ C<routing>,
+ C<scroll>,
+ C<search_type>,
+ C<seq_no_primary_term>,
+ C<size>,
+ C<sort>,
+ C<stats>,
+ C<stored_fields>,
+ C<suggest_field>,
+ C<suggest_mode>,
+ C<suggest_size>,
+ C<suggest_text>,
+ C<terminate_after>,
+ C<timeout>,
+ C<track_scores>,
+ C<track_total_hits>,
+ C<typed_keys>,
+ C<version>
+
+See the L<search reference|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html>
+for more information.
+
+Also see L<Search::Elasticsearch::Transport/send_get_body_as>.
+
+=head2 C<count()>
+
+ $results = $e->count(
+ index => 'index' | \@indices, # optional
+ type => 'type' | \@types, # optional
+
+ body => { query } # optional
+ )
+
+The C<count()> method returns the total count of all documents matching the
+query:
+
+ $results = $e->count(
+ body => {
+ query => {
+ match => { title => 'Elasticsearch clients' }
+ }
+ }
+ );
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<analyze_wildcard>,
+ C<analyzer>,
+ C<default_operator>,
+ C<df>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_throttled>,
+ C<ignore_unavailable>,
+ C<lenient>,
+ C<lowercase_expanded_terms>
+ C<min_score>,
+ C<preference>,
+ C<q>,
+ C<routing>,
+ C<terminate_after>
+
+See the L<count docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html>
+for more information.
+
+=head2 C<search_template()>
+
+ $results = $e->search_template(
+ index => 'index' | \@indices, # optional
+ type => 'type' | \@types, # optional
+
+ body => { search params } # required
+ );
+
+Perform a search by specifying a template (either predefined or defined
+within the C<body>) and parameters to use with the template, eg:
+
+ $results = $e->search_template(
+ body => {
+ source => {
+ query => {
+ match => {
+ "{{my_field}}" => "{{my_value}}"
+ }
+ },
+ size => "{{my_size}}"
+ },
+ params => {
+ my_field => 'foo',
+ my_value => 'bar',
+ my_size => 6
+ }
+ }
+ );
+
+See the L<search template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<explain>,
+ C<human>,
+ C<ignore_throttled>,
+ C<ignore_unavailable>,
+ C<preference>,
+ C<profile>,
+ C<rest_total_hits_as_int>,
+ C<scroll>,
+ C<search_type>,
+ C<typed_keys>
+
+=head2 C<render_search_template()>
+
+ $response = $e->render_search_template(
+ id => 'id', # optional
+ body => { template } # optional
+ );
+
+Renders the template, filling in the passed-in parameters and returns the resulting JSON, eg:
+
+ $results = $e->render_search_template(
+ body => {
+ source => {
+ query => {
+ match => {
+ "{{my_field}}" => "{{my_value}}"
+ }
+ },
+ size => "{{my_size}}"
+ },
+ params => {
+ my_field => 'foo',
+ my_value => 'bar',
+ my_size => 6
+ }
+ }
+ );
+
+See the L<search template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html>
+for more information.
+
+=head2 C<scroll()>
+
+ $results = $e->scroll(
+ scroll => '1m',
+ body => {
+ scroll_id => $id
+ }
+ );
+
+When a L</search()> has been performed with the
+C<scroll> parameter, the C<scroll()>
+method allows you to keep pulling more results until the results
+are exhausted.
+
+See L</scroll_helper()> and L<Search::Elasticsearch::Client::8_0::Scroll> for a helper utility
+which makes managing scroll requests much easier.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<rest_total_hits_as_int>,
+ C<scroll>,
+ C<scroll_id>
+
+See the L<scroll docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html>
+and the L<search_type docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search.html/search-request-search-type.html>
+for more information.
+
+=head2 C<clear_scroll()>
+
+ $response = $e->clear_scroll(
+ body => {
+ scroll_id => $id | \@ids # required
+ }
+ );
+
+The C<clear_scroll()> method can clear unfinished scroll requests, freeing
+up resources on the server.
+
+=head2 C<scroll_helper()>
+
+ $scroll_helper = $e->scroll_helper( @args );
+
+Returns a new instance of the class specified in the L</scroll_helper_class>,
+which defaults to L<Search::Elasticsearch::Client::8_0::Scroll>.
+
+
+=head2 C<msearch()>
+
+ $results = $e->msearch(
+ index => 'default_index' | \@indices, # optional
+ type => 'default_type' | \@types, # optional
+
+ body => [ searches ] # required
+ );
+
+The C<msearch()> method allows you to perform multiple searches in a single
+request. Similar to the L</bulk()> request, each search request in the
+C<body> consists of two hashes: the metadata hash then the search request
+hash (the same data that you'd specify in the C<body> of a L</search()>
+request). For instance:
+
+ $results = $e->msearch(
+ index => 'default_index',
+ type => ['default_type_1', 'default_type_2'],
+ body => [
+ # uses defaults
+ {},
+ { query => { match_all => {} }},
+
+ # uses a custom index
+ { index => 'not_the_default_index' },
+ { query => { match_all => {} }}
+ ]
+ );
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<max_concurrent_searches>,
+ C<max__concurrent_shard_requests>,
+ C<pre_filter_shard_size>,
+ C<rest_total_hits_as_int>,
+ C<search_type>,
+ C<typed_keys>
+
+See the L<msearch docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-multi-search.html>
+for more information.
+
+=head2 C<msearch_template()>
+
+ $results = $e->msearch_template(
+ index => 'default_index' | \@indices, # optional
+ type => 'default_type' | \@types, # optional
+
+ body => [ search_templates ] # required
+ );
+
+The C<msearch_template()> method allows you to perform multiple searches in a single
+request using search templates. Similar to the L</bulk()> request, each search
+request in the C<body> consists of two hashes: the metadata hash then the search request
+hash (the same data that you'd specify in the C<body> of a L</search()>
+request). For instance:
+
+ $results = $e->msearch(
+ index => 'default_index',
+ type => ['default_type_1', 'default_type_2'],
+ body => [
+ # uses defaults
+ {},
+ { source => { query => { match => { user => "{{user}}" }}} params => { user => 'joe' }},
+
+ # uses a custom index
+ { index => 'not_the_default_index' },
+ { source => { query => { match => { user => "{{user}}" }}} params => { user => 'joe' }},
+ ]
+ );
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<max_concurrent_searches>,
+ C<rest_total_hits_as_int>,
+ C<search_type>,
+ C<typed_keys>
+
+See the L<msearch-template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-multi-search.html>
+for more information.
+
+=head2 C<explain()>
+
+ $response = $e->explain(
+ index => 'my_index', # required
+ type => 'my_type', # required
+ id => 123, # required
+
+ body => { search } # required
+ );
+
+The C<explain()> method explains why the specified document did or
+did not match a query, and how the relevance score was calculated.
+For instance:
+
+ $response = $e->explain(
+ index => 'my_index',
+ type => 'my_type',
+ id => 123,
+ body => {
+ query => {
+ match => { title => 'Elasticsearch clients' }
+ }
+ }
+ );
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<analyze_wildcard>,
+ C<analyzer>,
+ C<default_operator>,
+ C<df>,
+ C<error_trace>,
+ C<human>,
+ C<lenient>,
+ C<parent>,
+ C<preference>,
+ C<q>,
+ C<routing>,
+ C<stored_fields>
+
+See the L<explain docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html>
+for more information.
+
+=head2 C<field_caps()>
+
+ $response = $e->field_caps(
+ index => 'index' | \@indices, # optional
+ body => { filters } # optional
+ );
+
+The C<field-caps> API returns field types and abilities, merged across indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<fields>,
+ C<human>,
+ C<ignore_unavailable>
+
+See the L<field-caps docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-field-caps.html>
+for more information.
+
+=head2 C<search_shards()>
+
+ $response = $e->search_shards(
+ index => 'index' | \@indices, # optional
+ )
+
+The C<search_shards()> method returns information about which shards on
+which nodes will execute a search request.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<local>,
+ C<preference>,
+ C<routing>
+
+See the L<search-shards docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/search-shards.html>
+for more information.
+
+=head2 C<rank_eval()>
+
+ $result = $e->rank_eval(
+ index => 'index' | \@indices, # optional
+ body => {...} # required
+ );
+
+The ranking evaluation API provides a way to execute test cases to determine whether search results
+are improving or worsening.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<filter_path>,
+ C<human>,
+ C<ignore_unavailable>
+
+See the L<rank-eval docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html>
+for more information.
+
+=head1 CRUD-BY-QUERY METHODS
+
+=head2 C<delete_by_query()>
+
+ $response = $e->delete_by_query(
+ index => 'index' | \@indices, # optional
+ type => 'type' | \@types, # optional,
+ body => { delete-by-query } # required
+ );
+
+The C<delete_by_query()> method deletes all documents which match the specified query.
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<allow_no_indices>,
+ C<analyze_wildcard>,
+ C<analyzer>,
+ C<conflicts>,
+ C<default_operator>,
+ C<df>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<from>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<lenient>,
+ C<preference>,
+ C<q>,
+ C<refresh>,
+ C<request_cache>,
+ C<requests_per_second>,
+ C<routing>,
+ C<scroll>,
+ C<scroll_size>,
+ C<search_timeout>,
+ C<search_type>,
+ C<size>,
+ C<slices>,
+ C<sort>,
+ C<stats>,
+ C<terminate_after>,
+ C<version>,
+ C<timeout>,
+ C<wait_for_active_shards>,
+ C<wait_for_completion>
+
+See the L<delete-by-query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html>
+for more information.
+
+=head2 C<delete_by_query_rethrottle()>
+
+ $response = $e->delete_by_query_rethrottle(
+ task_id => 'id' # required
+ requests_per_second => num
+ );
+
+The C<delete_by_query_rethrottle()> API is used to dynamically update the throtting
+of an existing delete-by-query request, identified by C<task_id>.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<requests_per_second>
+
+See the L<delete-by-query-rethrottle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html>
+for more information.
+
+=head2 C<reindex()>
+
+ $response = $e->reindex(
+ body => { reindex } # required
+ );
+
+The C<reindex()> API is used to index documents from one index or multiple indices
+to a new index.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<refresh>,
+ C<requests_per_second>,
+ C<slices>,
+ C<timeout>,
+ C<wait_for_active_shards>,
+ C<wait_for_completion>
+
+See the L<reindex docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html>
+for more information.
+
+=head2 C<reindex_rethrottle()>
+
+ $response = $e->delete_by_query_rethrottle(
+ task_id => 'id', # required
+ requests_per_second => num
+ );
+
+The C<reindex_rethrottle()> API is used to dynamically update the throtting
+of an existing reindex request, identified by C<task_id>.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<requests_per_second>
+
+See the L<reindex-rethrottle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html>
+for more information.
+
+
+=head2 C<update_by_query()>
+
+ $response = $e->update_by_query(
+ index => 'index' | \@indices, # optional
+ type => 'type' | \@types, # optional,
+ body => { update-by-query } # optional
+ );
+
+The C<update_by_query()> API is used to bulk update documents from one index or
+multiple indices using a script.
+
+Query string parameters:
+ C<_source>,
+ C<_source_excludes>,
+ C<_source_includes>,
+ C<allow_no_indices>,
+ C<analyze_wildcard>,
+ C<analyzer>,
+ C<conflicts>,
+ C<default_operator>,
+ C<df>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<from>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<lenient>,
+ C<pipeline>,
+ C<preference>,
+ C<q>,
+ C<refresh>,
+ C<request_cache>,
+ C<requests_per_second>,
+ C<routing>,
+ C<scroll>,
+ C<scroll_size>,
+ C<search_timeout>,
+ C<search_type>,
+ C<size>,
+ C<slices>,
+ C<sort>,
+ C<stats>,
+ C<terminate_after>,
+ C<timeout>,
+ C<version>,
+ C<version_type>,
+ C<wait_for_active_shards>,
+ C<wait_for_completion>
+
+See the L<update_by_query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html>
+for more information.
+
+=head2 C<update_by_query_rethrottle()>
+
+ $response = $e->update_by_query_rethrottle(
+ task_id => 'id' # required
+ requests_per_second => num
+ );
+
+The C<update_by_query_rethrottle()> API is used to dynamically update the throtting
+of an existing update-by-query request, identified by C<task_id>.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<requests_per_second>
+
+See the L<update-by-query-rethrottle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html>
+for more information.
+
+
+=head1 INDEXED SCRIPT METHODS
+
+Elasticsearch allows you to store scripts in the cluster state
+and reference them by id. The methods to manage indexed scripts are as follows:
+
+=head2 C<put_script()>
+
+ $result = $e->put_script(
+ id => 'id', # required
+ context => $context, # optional
+ body => { script } # required
+ );
+
+The C<put_script()> method is used to store a script in the cluster state. For instance:
+
+ $result = $e->put_scripts(
+ id => 'hello_world',
+ body => {
+ script => {
+ lang => 'painless',
+ source => q(return "hello world")
+ }
+ }
+ );
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+
+See the L<indexed scripts docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_indexed_scripts> for more.
+
+=head2 C<get_script()>
+
+ $script = $e->get_script(
+ id => 'id', # required
+ );
+
+Retrieve the indexed script from the cluster state.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
+
+See the L<indexed scripts docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_indexed_scripts> for more.
+
+=head2 C<delete_script()>
+
+ $script = $e->delete_script(
+ id => 'id', # required
+ );
+
+Delete the indexed script from the cluster state.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<indexed scripts docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_indexed_scripts> for more.
+
+=head2 C<scripts_painless_execute()>
+
+ $result = $e->scripts_painless_execute(
+ body => {...} # required
+ );
+
+The Painless execute API allows an arbitrary script to be executed and a result to be returned.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<painless execution docs|https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-execute-api.html> for more.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/AsyncSearch.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/AsyncSearch.pm
new file mode 100644
index 0000000..8f774a2
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/AsyncSearch.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::AsyncSearch;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('async_search');
+
+1;
+
+__END__
+
+# ABSTRACT: Async Search feature of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Async Search is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/async-search.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ $response = $es->async_search->get(
+ id => $id # required
+ )
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Autoscaling.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Autoscaling.pm
new file mode 100644
index 0000000..86ce3c9
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Autoscaling.pm
@@ -0,0 +1,43 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Autoscaling;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('autoscaling');
+
+1;
+
+__END__
+
+# ABSTRACT: Autoscaling feature of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Autoscaling is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/autoscaling-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ $response = $es->autoscaling->get();
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/CCR.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/CCR.pm
new file mode 100644
index 0000000..7fac733
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/CCR.pm
@@ -0,0 +1,228 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::CCR;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('ccr');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing cross-cluster replication APIs for Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+This module provides methods to use the cross-cluster replication feature.
+
+The full documentation for CCR is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ $response = $es->ccr->follow(
+ index => $index, # required
+ body => {...} # required
+ )
+
+The C<follow()> method creates a new follower index that is configured to follow the referenced leader index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<wait_for_active_shards>
+
+See the L<CCR follow docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-put-follow.html>
+for more information.
+
+
+=head2 C<pause_follow()>
+
+ $response = $es->ccr->pause_follow(
+ index => $index, # required
+ )
+
+The C<pause_follow()> method pauses following of an index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR pause follow docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-pause-follow.html>
+for more information.
+
+
+=head2 C<resume_follow()>
+
+ $response = $es->ccr->resume_follow(
+ index => $index, # required
+ )
+
+The C<resume_follow()> method resumes following of an index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR resume follow docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-resume-follow.html>
+for more information.
+
+
+=head2 C<unfollow()>
+
+ $response = $es->ccr->unfollow(
+ index => $index, # required
+ )
+
+The C<unfollow()> method converts a follower index into a normal index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR unfollow docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-unfollow.html>
+for more information.
+
+
+=head2 C<forget_follower()>
+
+ $response = $es->ccr->forget_follower(
+ index => $index, # required
+ )
+
+The C<forget_follower()> method removes the follower retention leases from the leader.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR forget_follower docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-forget-follower.html>
+for more information.
+
+=head1 STATS METHODS
+
+=head2 C<stats()>
+
+ $response = $es->ccr->stats()
+
+The C<stats()> method returns all stats related to cross-cluster replication.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR stats docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-stats.html>
+for more information.
+
+=head2 C<follow_stats()>
+
+ $response = $es->ccr->follow_stats(
+ index => $index | \@indices, # optional
+ )
+
+The C<follow_stats()> method returns shard-level stats about follower indices.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR follow stats docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-follow-stats.html>
+for more information.
+
+
+=head2 C<follow_info()>
+
+ $response = $es->ccr->follow_info(
+ index => $index | \@indices, # optional
+ )
+
+The C<follow_info()> method returns the parameters and the status for each follower index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR follow info docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-follow-info.html>
+for more information.
+
+=head1 AUTO-FOLLOW METHODS
+
+=head2 C<put_auto_follow_pattern()>
+
+ $response = $es->ccr->put_auto_follow_pattern(
+ name => $name # required
+ )
+
+The C<put_auto_follow_pattern()> method creates a new named collection of auto-follow patterns against the remote cluster specified in the request body.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR put auto follow pattern docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-put-auto-follow-pattern.html>
+for more information.
+
+
+=head2 C<get_auto_follow_pattern()>
+
+ $response = $es->ccr->get_auto_follow_pattern(
+ name => $name # optional
+ )
+
+The C<get_auto_follow_pattern()> method retrieves a named collection of auto-follow patterns, or all patterns.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR get auto follow pattern docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-auto-follow-pattern.html>
+for more information.
+
+=head2 C<delete_auto_follow_pattern()>
+
+ $response = $es->ccr->delete_auto_follow_pattern(
+ name => $name # required
+ )
+
+The C<delete_auto_follow_pattern()> method deletes a named collection of auto-follow patterns.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<CCR delete auto follow pattern docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-delete-auto-follow-pattern.html>
+for more information.
+
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Cat.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Cat.pm
new file mode 100644
index 0000000..930addd
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Cat.pm
@@ -0,0 +1,513 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Client::8_0::Direct::Cat;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('cat');
+
+#===================================
+sub help {
+#===================================
+ my ( $self, $params ) = parse_params(@_);
+ $params->{help} = 1;
+ my $defn = $self->api->{'cat.help'};
+ $self->perform_request( $defn, $params );
+}
+
+#===================================
+around 'perform_request' => sub {
+#===================================
+ my $orig = shift;
+ my $self = shift;
+ my ( $defn, $params ) = parse_params(@_);
+ if ( $params->{help} && $params->{help} ne 'false' ) {
+ $defn = { %$defn, parts => {} };
+ }
+
+ return $orig->( $self, $defn, $params );
+
+};
+
+1;
+
+__END__
+
+# ABSTRACT: A client for running cat debugging requests
+
+=head1 DESCRIPTION
+
+The C<cat> API in Elasticsearch provides information about your
+cluster and indices in a simple, easy to read text format, intended
+for human consumption.
+
+These APIs have a number of parameters in common:
+
+=over
+
+=item * C<help>
+
+Returns help about the API, eg:
+
+ say $e->cat->allocation(help => 1);
+
+=item * C<v>
+
+Includes the column headers in the output:
+
+ say $e->cat->allocation(v => 1);
+
+=item * C<h>
+
+Accepts a list of column names to be output, eg:
+
+ say $e->cat->indices(h => ['health','index']);
+
+=item * C<bytes>
+
+Formats byte-based values as bytes (C<b>), kilobytes (C<k>), megabytes
+(C<m>) or gigabytes (C<g>)
+
+
+=back
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 METHODS
+
+=head2 C<help()>
+
+ say $e->cat->help;
+
+Returns the list of supported C<cat> APIs
+
+=head2 C<aliases()>
+
+ say $e->cat->aliases(
+ name => 'name' | \@names # optional
+ );
+
+Returns information about index aliases, optionally limited to the specified
+index/alias names.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat aliases docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-aliases.html>
+for more information.
+
+=head2 C<allocation()>
+
+ say $e->cat->allocation(
+ node_id => 'node' | \@nodes # optional
+ );
+
+Provides a snapshot of how shards have located around the cluster and the
+state of disk usage.
+
+Query string parameters:
+ C<bytes>,
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat allocation docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-allocation.html>
+for more information.
+
+=head2 C<count()>
+
+ say $e->cat->count(
+ index => 'index' | \@indices # optional
+ );
+
+Provides quick access to the document count of the entire cluster, or
+individual indices.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat count docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-count.html>
+for more information.
+
+=head2 C<fielddata()>
+
+ say $e->cat->fielddata(
+ fields => 'field' | \@fields # optional
+ );
+
+Shows the amount of memory used by each of the specified `fields` (or all
+fields) loaded into fielddata.
+
+Query string parameters:
+ C<bytes>,
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat fielddata docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-fielddata.html>
+for more information.
+
+=head2 C<health()>
+
+ say $e->cat->health();
+
+Provides a snapshot of how shards have located around the cluster and the
+state of disk usage.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<ts>,
+ C<s>,
+ C<v>
+
+See the L<cat health docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-health.html>
+for more information.
+
+=head2 C<indices()>
+
+ say $e->cat->indices(
+ index => 'index' | \@indices # optional
+ );
+
+Provides a summary of index size and health for the whole cluster
+or individual indices
+
+Query string parameters:
+ C<bytes>,
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<health>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<pri>,
+ C<s>,
+ C<v>
+
+See the L<cat indices docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-indices.html>
+for more information.
+
+=head2 C<master()>
+
+ say $e->cat->master();
+
+Displays the master’s node ID, bound IP address, and node name.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat master docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-master.html>
+for more information.
+
+=head2 C<nodeattrs()>
+
+ say $e->cat->nodeattrs();
+
+Returns the node attributes set per node.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat nodeattrs docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodeattrs.html>
+for more information.
+
+=head2 C<nodes()>
+
+ say $e->cat->nodes();
+
+Provides a snapshot of all of the nodes in your cluster.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat nodes docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodes.html>
+for more information.
+
+=head2 C<pending_tasks()>
+
+ say $e->cat->pending_tasks();
+
+Returns any cluster-level tasks which are queued on the master.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<h>,
+ C<help>,
+ C<s>,
+ C<v>
+
+See the L<cat pending-tasks docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-pending-tasks.html>
+for more information.
+
+=head2 C<plugins()>
+
+ say $e->cat->plugins();
+
+Returns information about plugins installed on each node.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<h>,
+ C<help>,
+ C<s>,
+ C<v>
+
+See the L<cat plugins docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-plugins.html>
+for more information.
+
+=head2 C<recovery()>
+
+ say $e->cat->recovery(
+ index => 'index' | \@indices # optional
+ );
+
+Provides a view of shard replication. It will show information
+anytime data from at least one shard is copying to a different node.
+It can also show up on cluster restarts. If your recovery process seems
+stuck, try it to see if there’s any movement using C<recovery()>.
+
+Query string parameters:
+ C<bytes>,
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat recovery docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-recovery.html>
+for more information.
+
+=head2 C<repositories()>
+
+ say $e->cat->repositories()
+
+Provides a list of registered snapshot repositories.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat repositories docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-repositories.html>
+for more information.
+
+=head2 C<segments()>
+
+ say $e->cat->segments(
+ index => 'index' | \@indices # optional
+ );
+
+Provides low level information about the segments in the shards of an index.
+
+Query string parameters:
+ C<bytes>,
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<s>,
+ C<v>
+
+See the L<cat shards docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-segments.html>
+for more information.
+
+=head2 C<shards()>
+
+ say $e->cat->shards(
+ index => 'index' | \@indices # optional
+ );
+
+Provides a detailed view of what nodes contain which shards, the state and
+size of each shard.
+
+Query string parameters:
+ C<bytes>,
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat shards docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-shards.html>
+for more information.
+
+=head2 C<snapshots()>
+
+ say $e->cat->snapshots(
+ repository => 'repository' | \@repositories # optional
+ )
+
+Provides a list of all snapshots that belong to the specified repositories.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat snapshots docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-snapshots.html>
+for more information.
+
+=head2 C<tasks()>
+
+ say $e->cat->tasks()
+
+Provides a list of node-level tasks.
+
+Query string parameters:
+ C<actions>,
+ C<detailed>,
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<node_id>,
+ C<parent_node>,
+ C<parent_task>,
+ C<s>,
+ C<v>
+
+See the L<cat tasks docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
+for more information.
+
+=head2 C<templates()>
+
+ say $e->cat->templates(
+ name => $name # optional
+ )
+
+Provides a list of index templates.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<s>,
+ C<v>
+
+See the L<cat templates docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/templates.html>
+for more information.
+
+
+=head2 C<thread_pool()>
+
+ say $e->cat->thread_pool(
+ index => 'index' | \@indices # optional
+ );
+
+Shows cluster wide thread pool statistics per node. By default the C<active>,
+C<queue> and C<rejected> statistics are returned for the C<bulk>, C<index> and
+C<search> thread pools.
+
+Query string parameters:
+ C<error_trace>,
+ C<format>,
+ C<h>,
+ C<help>,
+ C<human>,
+ C<local>,
+ C<master_timeout>,
+ C<size>,
+ C<s>,
+ C<v>
+
+See the L<cat thread_pool docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cat-thread-pool.html>
+for more information.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Cluster.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Cluster.pm
new file mode 100644
index 0000000..7c965ca
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Cluster.pm
@@ -0,0 +1,250 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Cluster;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+__PACKAGE__->_install_api('cluster');
+
+1;
+
+__END__
+
+# ABSTRACT: A client for running cluster-level requests
+
+=head1 DESCRIPTION
+
+This module provides methods to make cluster-level requests, such as
+getting and setting cluster-level settings, manually rerouting shards,
+and retrieving for monitoring purposes.
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 METHODS
+
+=head2 C<health()>
+
+ $response = $e->cluster->health(
+ index => 'index' | \@indices # optional
+ );
+
+The C<health()> method is used to retrieve information about the cluster
+health, returning C<red>, C<yellow> or C<green> to indicate the state
+of the cluster, indices or shards.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<level>,
+ C<local>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>,
+ C<wait_for_events>,
+ C<wait_for_no_initializing_shards>,
+ C<wait_for_no_relocating_shards>,
+ C<wait_for_nodes>,
+ C<wait_for_status>
+
+See the L<cluster health docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html>
+for more information.
+
+=head2 C<stats()>
+
+ $response = $e->cluster->stats(
+ node_id => 'node' | \@nodes # optional
+ );
+
+Returns high-level cluster stats, optionally limited to the listed nodes.
+
+Query string parameters:
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>,
+ C<timeout>
+
+See the L<cluster stats docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-stats.html>
+for more information.
+
+=head2 C<get_settings()>
+
+ $response = $e->cluster->get_settings()
+
+The C<get_settings()> method is used to retrieve cluster-wide settings that
+have been set with the L</put_settings()> method.
+
+Query string parameters:
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>,
+ C<include_defaults>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<cluster settings docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-update-settings.html>
+for more information.
+
+=head2 C<put_settings()>
+
+ $response = $e->cluster->put_settings( %settings );
+
+The C<put_settings()> method is used to set cluster-wide settings, either
+transiently (which don't survive restarts) or permanently (which do survive
+restarts).
+
+For instance:
+
+ $response = $e->cluster->put_settings(
+ body => {
+ transient => { "discovery.zen.minimum_master_nodes" => 6 }
+ }
+ );
+
+Query string parameters:
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>
+
+See the L<cluster settings docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-update-settings.html>
+ for more information.
+
+=head2 C<state()>
+
+ $response = $e->cluster->state(
+ metric => $metric | \@metrics # optional
+ index => $index | \@indices # optional
+ );
+
+The C<state()> method returns the current cluster state from the master node,
+or from the responding node if C<local> is set to C<true>.
+
+It returns all metrics by default, but these can be limited to any of:
+ C<_all>,
+ C<blocks>,
+ C<metadata>,
+ C<nodes>,
+ C<routing_table>
+
+Metrics for indices can be limited to particular indices with the C<index>
+parameter.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<flat_settings>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<local>,
+ C<master_timeout>,
+ C<wait_for_metadata_version>,
+ C<wait_for_timeout>
+
+See the L<cluster state docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-state.html>
+for more information.
+
+=head2 C<allocation_explain()>
+
+ $response = $e->cluster->allocation_explain(
+ body => { ... shard selectors ...} # optional
+ );
+
+Returns information about why a shard is allocated or unallocated or why.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<include_disk_info>,
+ C<include_yes_decisions>
+
+See the L<cluster allocation explain docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-allocation-explain.html>
+for more information.
+
+=head2 C<pending_tasks()>
+
+ $response = $e->cluster->pending_tasks();
+
+Returns a list of cluster-level tasks still pending on the master node.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<local>,
+ C<master_timeout>
+
+See the L<pending tasks docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-pending.html>
+for more information.
+
+=head2 C<reroute()>
+
+ $e->cluster->reroute(
+ body => { commands }
+ );
+
+
+The C<reroute()> method is used to manually reallocate shards from one
+node to another. The C<body> should contain the C<commands> indicating
+which changes should be made. For instance:
+
+ $e->cluster->reroute(
+ body => {
+ commands => [
+ { move => {
+ index => 'test',
+ shard => 0,
+ from_node => 'node_1',
+ to_node => 'node_2
+ }},
+ { allocate => {
+ index => 'test',
+ shard => 1,
+ node => 'node_3'
+ }}
+ ]
+ }
+ );
+
+Query string parameters:
+ C<dry_run>,
+ C<error_trace>,
+ C<explain>,
+ C<human>,
+ C<master_timeout>,
+ C<metric>,
+ C<retry_failed>,
+ C<timeout>
+
+See the L<reroute docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-reroute.html>
+for more information.
+
+=head2 C<remote_info()>
+
+ $response = $e->cluster->remote_info();
+
+The C<remote_info()> API retrieves all of the configured remote cluster information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<remote_info docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-remote-info.html>
+for more information.
+
+
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/DanglingIndices.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/DanglingIndices.pm
new file mode 100644
index 0000000..f46df09
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/DanglingIndices.pm
@@ -0,0 +1,43 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::DanglingIndices;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('dangling_indices');
+
+1;
+
+__END__
+
+# ABSTRACT: Dangling indices feature of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Dangling Indices is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/master/indices.html#dangling-indices-api>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ $response = $es->dangling_indices->list_dangling_indices();
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Enrich.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Enrich.pm
new file mode 100644
index 0000000..e5e15cc
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Enrich.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Enrich;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('enrich');
+
+1;
+
+__END__
+
+# ABSTRACT: Enrich feature of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Enrich feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->enrich->get_policy(
+ 'name' => $name
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Eql.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Eql.pm
new file mode 100644
index 0000000..9bb96c0
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Eql.pm
@@ -0,0 +1,46 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Eql;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('eql');
+
+1;
+
+__END__
+
+# ABSTRACT: Eql feature of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Eql feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->eql->search(
+ 'index' => $index,
+ 'body' => {...}
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Features.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Features.pm
new file mode 100644
index 0000000..39c6d2f
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Features.pm
@@ -0,0 +1,57 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Features;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('features');
+
+1;
+
+__END__
+
+# ABSTRACT: Features API for Search::Elasticsearch 8.x
+
+
+=head1 SYNOPSIS
+
+ my $response = $es->features->get_features(...);
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<features>
+namespace, to support the API for the
+L<Features|https://www.elastic.co/guide/en/elasticsearch/reference/current/features-apis.html> plugin for Elasticsearch.
+
+=head1 METHODS
+
+The full documentation for the Features plugin is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/features-apis.html>
+
+=head2 C<explore()>
+
+ $response = $es->features->get_features();
+
+The C<get_features()> method Gets a list of features which can be included in snapshots
+using the feature_states field when creating a snapshot.
+
+See the L<get_features|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-features-api.html>
+for more information.
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Fleet.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Fleet.pm
new file mode 100644
index 0000000..c0f801a
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Fleet.pm
@@ -0,0 +1,51 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Fleet;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('fleet');
+
+1;
+
+__END__
+
+# ABSTRACT: Fleet APIs for Search::Elasticsearch 8.x
+
+The following APIs support Fleet’s use of Elasticsearch as a data store for internal
+agent and action data. These APIs are experimental and for internal use by Fleet only.
+
+=head1 SYNOPSIS
+
+ my $response = $es->fleet->search(...);
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<fleet>
+namespace, to support the API for the
+L<Fleet|https://www.elastic.co/guide/en/elasticsearch/reference/current/fleet-apis.html> plugin for Elasticsearch.
+
+=head1 METHODS
+
+The full documentation for the Fleet plugin is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/fleet-apis.html>
+
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Graph.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Graph.pm
new file mode 100644
index 0000000..0b02008
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Graph.pm
@@ -0,0 +1,66 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Graph;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('graph');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Graph API for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->graph->explore(...);
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<graph>
+namespace, to support the API for the
+L<Graph|https://www.elastic.co/guide/en/x-pack/current/xpack-graph.html> plugin for Elasticsearch.
+
+=head1 METHODS
+
+The full documentation for the Graph plugin is available here:
+L<https://www.elastic.co/guide/en/graph/current/index.html>
+
+=head2 C<explore()>
+
+ $response = $es->graph->explore(
+ index => $index | \@indices, # optional
+ type => $type | \@types, # optional
+ body => {...}
+ )
+
+The C<explore()> method allows you to discover vertices and connections which relate
+to your query.
+
+See the L<explore docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/graph-explore-api.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<routing>,
+ C<timeout>
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/ILM.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/ILM.pm
new file mode 100644
index 0000000..9b7f079
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/ILM.pm
@@ -0,0 +1,221 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::ILM;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use Search::Elasticsearch::Util qw(parse_params);
+use namespace::clean;
+__PACKAGE__->_install_api('ilm');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing index lifecycle management APIs for Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+This module provides methods to use the index lifecycle management feature.
+
+The full documentation for ILM is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/index-lifecycle-management.html>
+
+=head1 POLICY METHODS
+
+=head2 C<put_lifecycle()>
+
+ $response = $es->ilm->put_lifecycle(
+ policy => $policy # required
+ body => {...} # required
+ )
+
+The C<put_lifecycle()> method creates or updates a lifecycle policy.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM put_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-put-lifecycle.html>
+for more information.
+
+=head2 C<put_lifecycle()>
+
+ $response = $es->ilm->put_lifecycle(
+ policy => $policy # required
+ body => {...} # required
+ )
+
+The C<put_lifecycle()> method creates or updates a lifecycle policy.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM put_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-put-lifecycle.html>
+for more information.
+
+
+=head2 C<get_lifecycle()>
+
+ $response = $es->ilm->get_lifecycle(
+ policy => $policy # required
+ )
+
+The C<get_lifecycle()> method retrieves the specified policy
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM get_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-get-lifecycle.html>
+for more information.
+
+=head2 C<delete_lifecycle()>
+
+ $response = $es->ilm->delete_lifecycle(
+ policy => $policy # required
+ )
+
+The C<delete_lifecycle()> method deletes the specified policy
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM delete_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-remove-lifecycle.html>
+for more information.
+
+=head1 INDEX MANAGEMENT METHODS
+
+=head2 C<move_to_step()>
+
+ $response = $es->ilm->move_to_step(
+ index => $index, # required
+ body => {...} # required
+ )
+
+The C<move_to_step()> method triggers execution of a specific step in the lifecycle policy.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM move_to_step docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-move-to-step.html>
+for more information.
+
+
+=head2 C<retry()>
+
+ $response = $es->ilm->retry(
+ index => $index, # required
+ )
+
+The C<retry()> method retries executing the policy for an index that is in the ERROR step.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM retry docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-retry.html>
+for more information.
+
+
+=head2 C<remove_lifecycle()>
+
+ $response = $es->ilm->remove_lifecycle(
+ index => $index # required
+ )
+
+The C<remove_lifecycle()> method removes a lifecycle from the specified index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM remove_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-remove-lifecycle.html>
+for more information.
+
+=head2 C<explain_lifecycle()>
+
+ $response = $es->ilm->explain_lifecycle(
+ index => $index # required
+ )
+
+The C<explain_lifecycle()> method returns information about the index’s current lifecycle state.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM explain_lifecycle docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-explain-lifecycle.html>
+for more information.
+
+
+=head1 OPERATION MANAGEMENT APIS
+
+=head2 C<status()>
+
+ $response = $es->ilm->status;
+
+The C<status()> method returns the current operating mode for ILM.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM status docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-get-status.html>
+for more information.
+
+=head2 C<start()>
+
+ $response = $es->ilm->start;
+
+The C<start()> method starts the index lifecycle management process.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM start docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-start.html>
+for more information.
+
+=head2 C<stop()>
+
+ $response = $es->ilm->stop;
+
+The C<stop()> method stops the index lifecycle management process.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<ILM stop docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-stop.html>
+for more information.
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Indices.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Indices.pm
new file mode 100644
index 0000000..4ff568b
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Indices.pm
@@ -0,0 +1,968 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Indices;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+__PACKAGE__->_install_api('indices');
+
+1;
+
+__END__
+
+# ABSTRACT: A client for running index-level requests
+
+=head1 DESCRIPTION
+
+This module provides methods to make index-level requests, such as
+creating and deleting indices, managing type mappings, index settings,
+index templates and aliases.
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 INDEX METHODS
+
+=head2 C<create()>
+
+ $result = $e->indices->create(
+ index => 'my_index' # required
+
+ body => { # optional
+ index settings
+ mappings
+ aliases
+ }
+ );
+
+The C<create()> method is used to create an index. Optionally, index
+settings, type mappings, and aliases can be added at the same time.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<include_type_name>,
+ C<master_timeout>,
+ C<timeout>,
+ C<update_all_types>,
+ C<wait_for_active_shards>
+
+See the L<create index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html>
+for more information.
+
+=head2 C<get()>
+
+ $response = $e->indices->get(
+ index => 'index' | \@indices # required
+ );
+
+Returns the aliases, settings, and mappings for the specified indices.
+
+See the L<get index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-index.html>.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<flat_settings>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<include_defaults>,
+ C<include_type_name>,
+ C<local>,
+ C<master_timeout>
+
+=head2 C<exists()>
+
+ $bool = $e->indices->exists(
+ index => 'index' | \@indices # required
+ );
+
+The C<exists()> method returns C<1> or the empty string to indicate
+whether the specified index or indices exist.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<flat_settings>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<include_defaults>,
+ C<local>
+
+See the L<index exists docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-indices-exists.html>
+for more information.
+
+=head2 C<delete()>
+
+ $response = $e->indices->delete(
+ index => 'index' | \@indices # required
+ );
+
+The C<delete()> method deletes the specified indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<delete index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html>
+for more information.
+
+=head2 C<close()>
+
+ $response = $e->indices->close(
+ index => 'index' | \@indices # required
+ );
+
+The C<close()> method closes the specified indices, reducing resource usage
+but allowing them to be reopened later.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>
+ C<master_timeout>,
+ C<timeout>
+
+See the L<close index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html>
+for more information.
+
+=head2 C<open()>
+
+ $response = $e->indices->open(
+ index => 'index' | \@indices # required
+ );
+
+The C<open()> method opens closed indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<open index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html>
+for more information.
+
+=head2 C<rollover()>
+
+ $response = $e->indices->rollover(
+ alias => $alias, # required
+ new_index => $index, # optional
+ body => { rollover conditions } # optional
+ );
+
+Rollover an index pointed to by C<alias> if it meets rollover conditions
+(eg max age, max docs) to a new index name.
+
+Query string parameters:
+ C<dry_run>,
+ C<error_trace>,
+ C<human>,
+ C<include_type_name>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<rollover index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-rollover-index.html>
+for more information.
+
+=head2 C<shrink()>
+
+ $response = $e->shrink(
+ index => $index, # required
+ target => $target, # required
+ body => { mappings, settings aliases } # optional
+ );
+
+The shrink API shrinks the shards of an index down to a single shard (or to a factor
+of the original shards).
+
+Query string parameters:
+ C<copy_settings>,
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<shrink index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-shrink-index.html>
+for more information.
+
+=head2 C<split()>
+
+ $response = $e->split(
+ index => $index, # required
+ target => $target, # required
+ );
+
+The split API splits a shard into multiple shards.
+
+Query string parameters:
+ C<copy_settings>,
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<split index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-split-index.html>
+for more information.
+
+=head2 C<freeze()>
+
+ $response = $e->indices->freeze(
+ $index => $index # required
+ );
+
+The C<freeze()> API is used to freeze an index, which puts it in a state which has almost no
+overhead on the cluster.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<filter_path>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<freeze index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/freeze-index-api.html>
+for more information.
+
+=head2 C<unfreeze()>
+
+ $response = $e->indices->unfreeze(
+ $index => $index # required
+ );
+
+The C<unfreeze()> API is used to return a frozen index to its normal state.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<filter_path>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<timeout>,
+ C<wait_for_active_shards>
+
+See the L<unfreeze index docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/freeze-index-api.html>
+for more information.
+
+=head2 C<clear_cache()>
+
+ $response = $e->indices->clear_cache(
+ index => 'index' | \@indices # optional
+ );
+
+The C<clear_cache()> method is used to clear the in-memory filter, fielddata,
+or id cache for the specified indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<fielddata>,
+ C<fields>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<query>,
+ C<request>
+
+See the L<clear_cache docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clearcache.html>
+for more information.
+
+=head2 C<refresh()>
+
+ $response = $e->indices->refresh(
+ index => 'index' | \@indices # optional
+ );
+
+The C<refresh()> method refreshes the specified indices (or all indices),
+allowing recent changes to become visible to search. This process normally
+happens automatically once every second by default.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>
+
+See the L<refresh index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html>
+for more information.
+
+=head2 C<flush()>
+
+ $response = $e->indices->flush(
+ index => 'index' | \@indices # optional
+ );
+
+The C<flush()> method causes the specified indices (or all indices) to be
+written to disk with an C<fsync>, and clears out the transaction log.
+This process normally happens automatically.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<force>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<wait_if_ongoing>
+
+See the L<flush index docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html>
+for more information.
+
+=head2 C<flush_synced()>
+
+ $respnse = $e->indices->flush_synced(
+ index => 'index' | \@indices # optional
+ );
+
+The C<flush_synced()> method does a synchronised L<flush()> on the primaries and replicas of
+all the specified indices. In other words, after flushing it tries to write a C<sync_id>
+on the primaries and replicas to mark them as containing the same documents. During
+recovery, if a replica has the same C<sync_id> as the primary, then it doesn't need to check
+whether the segment files on primary and replica are the same, and it can move on
+directly to just replaying the translog. This can greatly speed up recovery.
+
+Synced flushes happens automatically in the background on indices that have not received any
+writes for a while, but the L<flush_synced()> method can be used to trigger this process
+manually, eg before shutting down. Any new commits immediately break the sync.
+
+See the L<flush synced docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-synced-flush.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>
+
+=head2 C<forcemerge()>
+
+ $response = $e->indices->forcemerge(
+ index => 'index' | \@indices # optional
+ );
+
+The C<forcemerge()> method rewrites all the data in an index into at most
+C<max_num_segments>. This is a very heavy operation and should only be run
+with care, and only on indices that are no longer being updated.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<flush>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<max_num_segments>,
+ C<only_expunge_deletes>
+
+See the L<forcemerge docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html>
+for more information.
+
+=head2 C<get_upgrade()>
+
+ $response = $e->indices->get_upgrade(
+ index => 'index' | \@indices # optional
+ );
+
+The C<get_upgrade()> method returns information about which indices need to be
+upgraded, which can be done with the C<upgrade()> method.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>
+
+See the L<upgrade docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-upgrade.html>
+for more information.
+
+=head2 C<upgrade()>
+
+ $response = $e->indices->upgrade(
+ index => 'index' | \@indices # optional
+ );
+
+The C<upgrade()> method upgrades all segments in the specified indices to the latest format.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<only_ancient_segments>,
+ C<wait_for_completion>
+
+See the L<upgrade docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-upgrade.html>
+for more information.
+
+=head1 MAPPING METHODS
+
+=head2 C<put_mapping()>
+
+ $response = $e->indices->put_mapping(
+ index => 'index' | \@indices # optional,
+ type => 'type', # optional
+
+ body => { mapping } # required
+ )
+
+The C<put_mapping()> method is used to create or update a type
+mapping on an existing index. Mapping updates are allowed to add new
+fields, but not to overwrite or change existing fields.
+
+For instance:
+
+ $response = $e->indices->put_mapping(
+ index => 'users',
+ type => 'user',
+ body => {
+ user => {
+ properties => {
+ name => { type => 'string' },
+ age => { type => 'integer' }
+ }
+ }
+ }
+ );
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<include_type_name>,
+ C<master_timeout>,
+ C<timeout>,
+ C<update_all_types>
+
+See the L<put_mapping docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html>
+for more information.
+
+
+=head2 C<get_mapping()>
+
+ $result = $e->indices->get_mapping(
+ index => 'index' | \@indices # optional,
+ type => 'type' | \@types # optional
+ );
+
+The C<get_mapping()> method returns the type definitions for one, more or
+all types in one, more or all indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<include_type_name>,
+ C<local>,
+ C<master_timeout>
+
+See the L<get_mapping docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-mapping.html>
+for more information.
+
+=head2 C<get_field_mapping()>
+
+ $result = $e->indices->get_field_mapping(
+ index => 'index' | \@indices # optional,
+ type => 'type' | \@types # optional,
+ fields => 'field' | \@fields # required
+
+ include_defaults => 0 | 1
+ );
+
+The C<get_field_mapping()> method returns the field definitions for one, more or
+all fields in one, more or all types and indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<include_defaults>,
+ C<include_type_name>,
+ C<local>
+
+See the L<get_mapping docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html>
+for more information.
+
+=head2 C<exists_type()>
+
+ $bool = $e->indices->exists_type(
+ index => 'index' | \@indices # required,
+ type => 'type' | \@types # required
+ );
+
+The C<exists_type()> method checks for the existence of all specified types
+in all specified indices, and returns C<1> or the empty string.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<local>
+
+See the L<exists_type docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-types-exists.html>
+for more information.
+
+=head1 ALIAS METHODS
+
+=head2 C<update_aliases()>
+
+ $response = $e->indices->update_aliases(
+ body => { actions } # required
+ );
+
+The C<update_aliases()> method changes (by adding or removing) multiple
+index aliases atomically. For instance:
+
+ $response = $e->indices->update_aliases(
+ body => {
+ actions => [
+ { add => { alias => 'current', index => 'logs_2013_09' }},
+ { remove => { alias => 'current', index => 'logs_2013_08' }}
+ ]
+ }
+ );
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<update_aliases docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
+for more information.
+
+=head2 C<put_alias()>
+
+ $response = $e->indices->put_alias(
+ index => 'index' | \@indices, # required
+ name => 'alias', # required
+
+ body => { alias defn } # optional
+ );
+
+The C<put_alias()> method creates an index alias. For instance:
+
+ $response = $e->indices->put_alias(
+ index => 'my_index',
+ name => 'twitter',
+ body => {
+ filter => { term => { user_id => 'twitter' }}
+ }
+ );
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<put_alias docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
+for more information.
+
+=head2 C<get_alias()>
+
+ $result = $e->indices->get_alias(
+ index => 'index' | \@indices, # optional
+ name => 'alias' | \@aliases # optional
+ );
+
+The C<get_alias()> method returns the alias definitions for the specified
+aliases in the specified indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<local>
+
+See the L<get_alias docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
+for more information.
+
+=head2 C<exists_alias()>
+
+ $bool = $e->indices->exists_alias(
+ index => 'index' | \@indices, # optional
+ name => 'alias' | \@aliases # required
+ );
+
+The C<exists_alias()> method returns C<1> or the empty string depending on
+whether the specified aliases exist in the specified indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<local>
+
+See the L<exists_alias docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
+for more information.
+
+=head2 C<delete_alias()>
+
+ $response = $e->indices->delete_alias(
+ index => 'index' | \@indices # required,
+ name => 'alias' | \@aliases # required
+ );
+
+The C<delete_alias()> method deletes one or more aliases from one or more
+indices.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<delete_alias docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
+for more information.
+
+=head1 SETTINGS METHODS
+
+=head2 C<put_settings()>
+
+ $response = $e->indices->put_settings(
+ index => 'index' | \@indices # optional
+
+ body => { settings }
+ );
+
+The C<put_settings()> method sets the index settings for the specified
+indices or all indices. For instance:
+
+ $response = $e->indices->put_settings(
+ body => {
+ "index.refresh_interval" => -1
+ }
+ );
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<flat_settings>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<preserve_existing>,
+ C<timeout>
+
+See the L<put_settings docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html>
+for more information.
+
+=head2 C<get_settings()>
+
+ $result = $e->indices->get_settings(
+ index => 'index' | \@indices # optional
+ name => 'name' | \@names # optional
+ );
+
+The C<get_settings()> method retrieves the index settings for the specified
+indices or all indices.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<flat_settings>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<include_defaults>,
+ C<local>,
+ C<master_timeout>
+
+See the L<get_settings docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-settings.html>
+for more information.
+
+=head1 TEMPLATE METHODS
+
+=head2 C<put_template()>
+
+ $response = $e->indices->put_template(
+ name => 'template' # required
+ body => { template defn } # required
+ );
+
+The C<put_template()> method is used to create or update index templates.
+
+Query string parameters:
+ C<create>,
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>,
+ C<include_type_name>,
+ C<master_timeout>,
+ C<order>,
+ C<timeout>
+
+See the L<put_template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html>
+for more information.
+
+=head2 C<get_template()>
+
+ $result = $e->indices->get_template(
+ name => 'template' | \@templates # optional
+ );
+
+The C<get_template()> method is used to retrieve a named template.
+
+Query string parameters:
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>,
+ C<include_type_name>,
+ C<local>,
+ C<master_timeout>
+
+See the L<get_template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html>
+for more information.
+
+=head2 C<exists_template()>
+
+ $result = $e->indices->exists_template(
+ name => 'template' # optional
+ );
+
+The C<exists_template()> method is used to check whether the named template exists.
+
+Query string parameters:
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>,
+ C<local>,
+ C<master_timeout>
+
+See the L<get_template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html>
+for more information.
+
+=head2 C<delete_template()>
+
+ $response = $e->indices->delete_template(
+ name => 'template' # required
+ );
+
+The C<delete_template()> method is used to delete a named template.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>,
+ C<version>,
+ C<version_type>
+
+See the L<delete_template docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html>
+for more information.
+
+
+=head1 STATS METHODS
+
+=head2 C<stats()>
+
+ $result = $e->indices->stats(
+ index => 'index' | \@indices # optional
+ metric => 'metric' | \@metrics # optional
+ );
+
+The C<stats()> method returns statistical information about one, more or all
+indices. By default it returns all metrics, but you can limit which metrics
+are returned by specifying the C<metric>.
+
+Allowed metrics are:
+ C<_all>,
+ C<completion>
+ C<docs>,
+ C<fielddata>,
+ C<filter_cache>,
+ C<flush>,
+ C<get>,
+ C<id_cache>,
+ C<indexing>,
+ C<merge>,
+ C<percolate>,
+ C<query_cache>,
+ C<refresh>,
+ C<request_cache>,
+ C<search>,
+ C<segments>,
+ C<store>
+
+
+Query string parameters:
+ C<completion_fields>,
+ C<error_trace>,
+ C<fielddata_fields>,
+ C<fields>,
+ C<groups>,
+ C<human>,
+ C<include_segment_file_sizes>,
+ C<level>,
+ C<types>
+
+See the L<stats docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-stats.html>
+for more information.
+
+=head2 C<recovery()>
+
+ $result = $e->indices->recovery(
+ index => 'index' | \@indices # optional
+ );
+
+Provides insight into on-going shard recoveries.
+
+Query string parameters:
+ C<active_only>,
+ C<detailed>,
+ C<error_trace>,
+ C<human>
+
+See the L<recovery docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-recovery.html>
+for more information.
+
+=head2 C<segments()>
+
+ $result = $e->indices->segments(
+ index => 'index' | \@indices # optional
+ );
+
+The C<segments()> method is used to return information about the segments
+that an index contains.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<verbose>
+
+See the L<segments docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-segments.html>
+for more information.
+
+=head2 C<shard_stores()>
+
+ $result = $e->indices->shard_stores(
+ index => 'index' | \@indices # optional
+ );
+
+The C<shard_stores()> method is used to find out which nodes contain
+copies of which shards, whether the shards are allocated or not.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<status>
+
+See the L<shard_stores docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-shards-stores.html>
+for more information.
+
+=head1 QUERY AND ANALYSIS METHODS
+
+=head2 C<analyze()>
+
+ $result = $e->indices->analyze(
+ index => 'index' # optional,
+ body => 'text to analyze'
+ );
+
+The C<analyze()> method passes the text in the C<body> through the specified
+C<analyzer>, C<tokenizer> or token C<filter> - which may be global, or associated
+with a particular index or field - and returns the tokens. Very useful
+for debugging analyzer configurations.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<analyze docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html>
+for more information.
+
+=head2 C<validate_query()>
+
+ $result = $e->indices->validate_query(
+ index => 'index' | \@indices, # optional
+ type => 'type' | \@types, # optional
+
+ body => { query }
+ );
+
+The C<validate_query()> method accepts a query in the C<body> and checks
+whether the query is valid or not. Most useful when C<explain> is set
+to C<true>, in which case it includes an execution plan in the output.
+
+Query string parameters:
+ C<all_shards>,
+ C<allow_no_indices>,
+ C<analyze_wildcard>,
+ C<analyzer>,
+ C<default_operator>,
+ C<df>,
+ C<error_trace>,
+ C<explain>,
+ C<expand_wildcards>,
+ C<ignore_unavailable>,
+ C<lenient>,
+ C<q>,
+ C<rewrite>
+
+See the L<validate_query docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-validate.html>
+for more information.
+
+
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Ingest.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Ingest.pm
new file mode 100644
index 0000000..97bae31
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Ingest.pm
@@ -0,0 +1,130 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Client::8_0::Direct::Ingest;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+__PACKAGE__->_install_api('ingest');
+
+1;
+
+__END__
+
+# ABSTRACT: A client for accessing the Ingest API
+
+=head1 DESCRIPTION
+
+This module provides methods to access the Ingest API, such as creating,
+getting, deleting and simulating ingest pipelines.
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 METHODS
+
+=head2 C<put_pipeline()>
+
+ $response = $e->ingest->put_pipeline(
+ id => $id, # required
+ body => { pipeline defn } # required
+ );
+
+The C<put_pipeline()> method creates or updates a pipeline with the specified ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<put pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/put-pipeline-api.html>
+for more information.
+
+=head2 C<get_pipeline()>
+
+ $response = $e->ingest->get_pipeline(
+ id => \@id, # optional
+ );
+
+The C<get_pipeline()> method returns pipelines with the specified IDs (or all pipelines).
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<get pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-pipeline-api.html>
+for more information.
+
+=head2 C<delete_pipeline()>
+
+ $response = $e->ingest->delete_pipeline(
+ id => $id, # required
+ );
+
+The C<delete_pipeline()> method deletes the pipeline with the specified ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<delete pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/delete-pipeline-api.html>
+for more information.
+
+=head2 C<simulate()>
+
+ $response = $e->ingest->put_pipeline(
+ id => $id, # optional
+ body => { simulate args } # required
+ );
+
+The C<simulate()> method executes the pipeline specified by ID or inline in the body
+against the docs provided in the body and provides debugging output of the execution
+process.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<verbose>
+
+See the L<simulate pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/simulate-pipeline-api.html>
+for more information.
+
+
+=head2 C<simulate()>
+
+ $response = $e->ingest->put_pipeline(
+ id => $id, # optional
+ body => { simulate args } # required
+ );
+
+The C<simulate()> method executes the pipeline specified by ID or inline in the body
+against the docs provided in the body and provides debugging output of the execution
+process.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<verbose>
+
+See the L<simulate pipeline docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/simulate-pipeline-api.html>
+for more information.
+
+=head2 C<processor_grok>
+
+ $response = $e->inges->processor_grok()
+
+Retrieves the configured patterns associated with the Grok processor.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<grok processor docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/grok-processor.html>
+for more information.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/License.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/License.pm
new file mode 100644
index 0000000..8d326fa
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/License.pm
@@ -0,0 +1,134 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::License;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('license');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing License API for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->license->get();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<license>
+namespace, to support the API for the License plugin for Elasticsearch.
+
+=head1 METHODS
+
+The full documentation for the License plugin is available here:
+L<https://www.elastic.co/guide/en/x-pack/current/license-management.html>
+
+=head2 C<get()>
+
+ $response = $es->license->get()
+
+The C<get()> method returns the currently installed license.
+
+See the L<license.get docs|https://www.elastic.co/guide/en/x-pack/current/listing-licenses.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<local>
+
+=head2 C<post()>
+
+ $response = $es->license->post(
+ body => {...} # required
+ );
+
+The C<post()> method adds or updates the license for the cluster. The C<body>
+can be passed as JSON or as a string.
+
+See the L<license.put docs|https://www.elastic.co/guide/en/x-pack/current/installing-license.html>
+for more information.
+
+Query string parameters:
+ C<acknowledge>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_basic_status()>
+
+ $response = $es->license->get_basic_status()
+
+This API enables you to check the status of your basic license.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<get-basic-status docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-basic-status.html> for more.
+
+=head2 C<post_start_basic()>
+
+ $response = $es->license->post_start_basic()
+
+This API enables you to initiate an indefinite basic license, which gives access to all the basic features.
+
+Query string parameters:
+ C<acknowledge>,
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<post-start-basic docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/start-basic.html> for more.
+
+
+=head2 C<get_trial_status()>
+
+ $response = $es->license->get_trial_status()
+
+This API enables you to check the status of your trial license.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<get-trial-status docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-trial-status.html> for more.
+
+=head2 C<post_start_trial()>
+
+ $response = $es->license->post_start_trial()
+
+This API enables you to upgrade from a basic license to a 30-day trial license, which gives
+access to the platinum features.
+
+Query string parameters:
+ C<acknowledge>,
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<post-start-trial docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/start-trial.html> for more.
+
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Logstash.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Logstash.pm
new file mode 100644
index 0000000..95eefca
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Logstash.pm
@@ -0,0 +1,47 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Logstash;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('logstash');
+
+1;
+
+__END__
+
+# ABSTRACT: Logstash API for Search::Elasticsearch 8.x
+
+
+=head1 SYNOPSIS
+
+ my $response = $es->logstash->get_pipeline(...);
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<logstash>
+namespace, to support the API for the
+L<Logstash|https://www.elastic.co/guide/en/elasticsearch/reference/current/logstash-apis.html> plugin for Elasticsearch.
+
+=head1 METHODS
+
+The full documentation for the Features plugin is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/logstash-apis.html>
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/ML.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/ML.pm
new file mode 100644
index 0000000..5cbdcce
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/ML.pm
@@ -0,0 +1,842 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::ML;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('ml');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing ML API for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->ml->start_datafeed(...)
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with an C<ml>
+namespace, to support the
+L<Machine Learning APIs|https://www.elastic.co/guide/en/x-pack/7.0/xpack-ml.html>.
+
+The full documentation for the ML feature is available here:
+L<https://www.elastic.co/guide/en/x-pack/7.0/xpack-ml.html>
+
+=head1 DATAFEED METHODS
+
+=head2 C<put_datafeed()>
+
+ $response = $es->ml->put_datafeed(
+ datafeed_id => $id # required
+ body => {...} # required
+ )
+
+The C<put_datafeed()> method enables you to instantiate a datafeed.
+
+See the L<put_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_datafeed()>
+
+ $response = $es->xpack->ml->delete_datafeed(
+ datafeed_id => $id # required
+ )
+
+The C<delete_datafeed()> method enables you to delete a datafeed.
+
+See the L<delete_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<force>,
+ C<human>
+
+=head2 C<start_datafeed()>
+
+ $response = $es->ml->start_datafeed(
+ datafeed_id => $id # required
+ )
+
+The C<start_datafeed()> method enables you to start a datafeed.
+
+See the L<start_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-start-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<end>,
+ C<error_trace>,
+ C<human>,
+ C<start>,
+ C<timeout>
+
+=head2 C<stop_datafeed()>
+
+ $response = $es->ml->stop_datafeed(
+ datafeed_id => $id # required
+ )
+
+The C<stop_datafeed()> method enables you to stop a datafeed.
+
+See the L<stop_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-stop-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_datafeeds>,
+ C<error_trace>,
+ C<force>,
+ C<human>,
+ C<timeout>
+
+=head2 C<get_datafeeds()>
+
+ $response = $es->ml->get_datafeeds(
+ datafeed_id => $id # optional
+ )
+
+The C<get_datafeeds()> method enables you to retrieve configuration information for datafeeds.
+
+See the L<get_datafeeds docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_datafeeds>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_datafeed_stats()>
+
+ $response = $es->ml->get_datafeed_stats(
+ datafeed_id => $id # optional
+ )
+
+The C<get_datafeed_stats()> method enables you to retrieve configuration information for datafeeds.
+
+See the L<get_datafeed_stats docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-datafeed-stats.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_datafeeds>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<preview_datafeed()>
+
+ $response = $es->ml->preview_datafeed(
+ datafeed_id => $id # required
+ )
+
+The C<preview_datafeed()> method enables you to preview a datafeed.
+
+See the L<preview_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-preview-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<update_datafeed()>
+
+ $response = $es->ml->update_datafeed(
+ datafeed_id => $id # required
+ body => {...} # required
+ )
+
+The C<update_datafeed()> method enables you to update certain properties of a datafeed.
+
+See the L<update_datafeed docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-datafeed.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 JOB METHODS
+
+=head2 C<put_job()>
+
+ $response = $es->ml->put_job(
+ job_id => $id # required
+ body => {...} # required
+ )
+
+The C<put_job()> method enables you to instantiate a job.
+
+See the L<put_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-job.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_job()>
+
+ $response = $es->ml->delete_job(
+ job_id => $id # required
+ )
+
+The C<delete_job()> method enables you to delete a job.
+
+See the L<delete_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-job.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<force>,
+ C<human>,
+ C<wait_for_completion>
+
+=head2 C<open_job()>
+
+ $response = $es->ml->open_job(
+ job_id => $id # required
+ )
+
+The C<open_job()> method enables you to open a closed job.
+
+See the L<open_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-open-job.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<close_job()>
+
+ $response = $es->ml->close_job(
+ job_id => $id # required
+ )
+
+The C<close_job()> method enables you to close an open job.
+
+See the L<close_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-close-job.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_jobs>,
+ C<error_trace>,
+ C<force>,
+ C<human>,
+ C<timeout>
+
+=head2 C<get_jobs()>
+
+ $response = $es->ml->get_jobs(
+ job_id => $id # optional
+ )
+
+The C<get_jobs()> method enables you to retrieve configuration information for jobs.
+
+See the L<get_jobs docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_jobs>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_job_stats()>
+
+ $response = $es->ml->get_jobs_stats(
+ job_id => $id # optional
+ )
+
+The C<get_jobs_stats()> method enables you to retrieve usage information for jobs.
+
+See the L<get_job_statss docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job-stats.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_jobs>,
+ C<error_trace>,
+ C<human>
+
+
+=head2 C<flush_job()>
+
+ $response = $es->ml->flush_job(
+ job_id => $id # required
+ )
+
+The C<flush_job()> method forces any buffered data to be processed by the job.
+
+See the L<flush_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-flush-job.html>
+for more information.
+
+Query string parameters:
+ C<advance_time>,
+ C<calc_interm>,
+ C<end>,
+ C<error_trace>,
+ C<human>,
+ C<skip_time>,
+ C<start>
+
+=head2 C<post_data()>
+
+ $response = $es->ml->post_data(
+ job_id => $id # required
+ body => [data] # required
+ )
+
+The C<post_data()> method enables you to send data to an anomaly detection job for analysis.
+
+See the L<post_data docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-post-data.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<reset_end>,
+ C<reset_start>
+
+=head2 C<update_job()>
+
+ $response = $es->ml->update_job(
+ job_id => $id # required
+ body => {...} # required
+ )
+
+The C<update_job()> method enables you to update certain properties of a job.
+
+See the L<update_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-job.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_expired_data>
+
+ $response = $es->ml->delete_expired_data(
+ )
+
+The C<delete_expired_data()> method deletes expired machine learning data.
+
+See the L<delete_expired_data docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-expired-data.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+
+=head1 CALENDAR METHODS
+
+=head2 C<put_calendar()>
+
+ $response = $es->ml->put_calendar(
+ calendar_id => $id # required
+ body => {...} # optional
+ )
+
+The C<put_calendar()> method creates a new calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<put calendar docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-calendar.html>
+for more information.
+
+=head2 C<delete_calendar()>
+
+ $response = $es->ml->delete_calendar(
+ calendar_id => $id # required
+ )
+
+The C<delete_calendar()> method deletes the specified calendar
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<delete_calendar docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-calendar.html>
+for more information.
+
+=head2 C<put_calendar_job()>
+
+ $response = $es->ml->put_calendar_job(
+ calendar_id => $id, # required
+ job_id => $id # required
+ )
+
+The C<put_calendar_job()> method adds a job to a calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<put_calendar_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-calendar-job.html>
+for more information.
+
+=head2 C<delete_calendar_job()>
+
+ $response = $es->ml->delete_calendar_job(
+ calendar_id => $id, # required
+ job_id => $id # required
+ )
+
+The C<delete_calendar_job()> method deletes a job from a calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<delete_calendar_job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-calendar-job.html>
+for more information.
+
+=head2 C<put_calendar_event()>
+
+ $response = $es->ml->post_calendar_events(
+ calendar_id => $id, # required
+ body => {...} # required
+ )
+
+The C<post_calendar_events()> method adds scheduled events to a calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<post_calendar_events docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-post-calendar-events.html>
+for more information.
+
+
+=head2 C<delete_calendar_event()>
+
+ $response = $es->ml->delete_calendar_event(
+ calendar_id => $id, # required
+ event_id => $id # required
+ )
+
+The C<delete_calendar_event()> method deletes an event from a calendar.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<delete_calendar_event docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-calendar-event.html>
+for more information.
+
+=head2 C<get_calendars()>
+
+ $response = $es->ml->get_calendars(
+ calendar_id => $id, # optional
+ )
+
+The C<get_calendars()> method returns the specified calendar or all calendars.
+
+Query string parameters:
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<size>
+
+See the L<get_calendars docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-calendar-event.html>
+for more information.
+
+=head2 C<get_calendar_events()>
+
+ $response = $es->ml->get_calendar_events(
+ calendar_id => $id, # required
+ )
+
+The C<get_calendar_events()> method retrieves events from a calendar.
+
+Query string parameters:
+ C<end>,
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<job_id>,
+ C<size>,
+ C<start>
+
+See the L<get_calendar_events docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-calendar-event.html>
+for more information.
+
+=head1 FILTER METHODS
+
+=head2 C<put_filter()>
+
+ $response = $es->ml->put_filter(
+ filter_id => $id, # required
+ body => {...} # required
+ )
+
+The C<put_filter()> method creates a named filter.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<put_filter docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-filter.html>
+for more information.
+
+=head2 C<update_filter()>
+
+ $response = $es->ml->update_filter(
+ filter_id => $id, # required
+ body => {...} # required
+ )
+
+The C<update_filter()> method updates the description of a filter, adds items, or removes items.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<update_filter docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-filter.html>
+for more information.
+
+=head2 C<get_filters()>
+
+ $response = $es->ml->get_filters(
+ filter_id => $id, # optional
+ )
+
+The C<get_filters()> method retrieves a named filter or all filters.
+
+Query string parameters:
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<size>
+
+See the L<get_filters docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-filters.html>
+for more information.
+
+=head2 C<delete_filter()>
+
+ $response = $es->ml->delete_filter(
+ filter_id => $id, # required
+ )
+
+The C<delete_filter()> method deletes a named filter.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+See the L<delete_filters docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-filter.html>
+for more information.
+
+=head1 FORECAST METHODS
+
+=head2 C<forecast()>
+
+ $response = $es->ml->forecast(
+ job_id => $id # required
+ )
+
+The C<forecast()> method enables you to create a new forecast
+
+See the L<forecast docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-forecast.html>
+for more information.
+
+Query string parameters:
+ C<duration>,
+ C<error_trace>,
+ C<expires_in>,
+ C<human>
+
+=head2 C<delete_forecast()>
+
+ $response = $es->ml->delete_forecast(
+ forecast_id => $id, # required
+ job_id => $id # required
+ )
+
+The C<delete_forecast()> method enables you to delete an existing forecast.
+
+See the L<delete_forecast docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-forecast.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_forecasts>,
+ C<error_trace>,
+ C<human>,
+ C<timeout>
+
+=head1 MODEL SNAPSHOT METHODS
+
+=head2 C<delete_model_snapshot()>
+
+ $response = $es->ml->delete_model_snapshot(
+ snapshot_id => $id # required
+ )
+
+The C<delete_model_snapshot()> method enables you to delete an existing model snapshot.
+
+See the L<delete_model_snapshot docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-snapshot.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_model_snapshots()>
+
+ $response = $es->ml->get_model_snapshots(
+ job_id => $job_id, # required
+ snapshot_id => $snapshot_id # optional
+ )
+
+The C<get_model_snapshots()> method enables you to retrieve information about model snapshots.
+
+See the L<get_model_snapshots docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-snapshot.html>
+for more information.
+
+Query string parameters:
+ C<desc>,
+ C<end>,
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<size>,
+ C<sort>,
+ C<start>
+
+=head2 C<revert_model_snapshot()>
+
+ $response = $es->ml->revert_model_snapshot(
+ job_id => $job_id, # required
+ snapshot_id => $snapshot_id # required
+ )
+
+The C<revert_model_snapshots()> method enables you to revert to a specific snapshot.
+
+See the L<revert_model_snapshot docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-revert-snapshot.html>
+for more information.
+
+Query string parameters:
+ C<delete_intervening_results>,
+ C<error_trace>,
+ C<human>
+
+=head2 C<update_model_snapshot()>
+
+ $response = $es->ml->update_model_snapshot(
+ job_id => $job_id, # required
+ snapshot_id => $snapshot_id # required
+ )
+
+The C<update_model_snapshots()> method enables you to update certain properties of a snapshot.
+
+See the L<update_model_snapshot docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-snapshot.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 RESULT METHODS
+
+=head2 C<get_buckets()>
+
+ $response = $es->ml->get_buckets(
+ job_id => $job_id, # required
+ timestamp => $timestamp # optional
+ )
+
+The C<get_buckets()> method enables you to retrieve job results for one or more buckets.
+
+See the L<get_buckets docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-bucket.html>
+for more information.
+
+Query string parameters:
+ C<anomaly_score>,
+ C<desc>,
+ C<end>,
+ C<error_trace>,
+ C<exclude_interim>,
+ C<expand>,
+ C<from>,
+ C<human>,
+ C<size>,
+ C<sort>,
+ C<start>
+
+=head2 C<get_overall_buckets()>
+
+ $response = $es->ml->get_overall_buckets(
+ job_id => $job_id, # required
+ )
+
+The C<get_overall_buckets()> method retrieves overall bucket results that summarize the bucket results of multiple jobs.
+
+See the L<get_overall_buckets docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-overall-buckets.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_jobs>,
+ C<bucket_span>,
+ C<end>,
+ C<error_trace>,
+ C<exclude_interim>,
+ C<human>,
+ C<overall_score>,
+ C<start>,
+ C<top_n>
+
+=head2 C<get_categories()>
+
+ $response = $es->ml->get_categories(
+ job_id => $job_id, # required
+ category_id => $category_id # optional
+ )
+
+The C<get_categories()> method enables you to retrieve job results for one or more categories.
+
+See the L<get_categories docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-category.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<from>,
+ C<human>,
+ C<size>
+
+
+=head2 C<get_influencers()>
+
+ $response = $es->ml->get_influencers(
+ job_id => $job_id, # required
+ )
+
+The C<get_influencers()> method enables you to retrieve job results for one or more influencers.
+
+See the L<get_influencers docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-influencer.html>
+for more information.
+
+Query string parameters:
+ C<desc>,
+ C<end>,
+ C<error_trace>,
+ C<exclude_interim>,
+ C<expand>,
+ C<from>,
+ C<human>,
+ C<influencer_score>,
+ C<size>,
+ C<sort>,
+ C<start>
+
+=head2 C<get_records()>
+
+ $response = $es->ml->get_records(
+ job_id => $job_id, # required
+ )
+
+The C<get_records()> method enables you to retrieve anomaly records for a job.
+
+See the L<get_records docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-record.html>
+for more information.
+
+Query string parameters:
+ C<desc>,
+ C<end>,
+ C<error_trace>,
+ C<exclude_interim>,
+ C<expand>,
+ C<from>,
+ C<human>,
+ C<record_score>,
+ C<size>,
+ C<sort>,
+ C<start>
+
+=head1 FILE STRUCTURE METHODS
+
+=head2 C<find_file_structure>
+
+
+ $response = $es->ml->find_file_structure(
+ body => { ... }, # required
+ )
+
+The C<find_file_structure()> method finds the structure of a text file which contains data
+that is suitable to be ingested into Elasticsearch.
+
+See the L<find_file_structure docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-find-file-structure.html>
+for more information.
+
+Query string parameters:
+ C<charset>,
+ C<column_names>,
+ C<delimiter>,
+ C<error_trace>,
+ C<explain>,
+ C<format>,
+ C<grok_pattern>,
+ C<has_header_row>,
+ C<human>,
+ C<lines_to_sample>,
+ C<quote>,
+ C<should_trim_fields>,
+ C<timeout>,
+ C<timestamp_field>,
+ C<timestamp_format>
+
+
+
+=head1 INFO METHODS
+
+
+=head2 C<info>
+
+ $response = $es->ml->info();
+
+The C<info()> method returns defaults and limits used by machine learning.
+
+See the L<find_file_structure docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/get-ml-info.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 UPGRADE METHODS
+
+=head2 C<set_upgrade_mode>
+
+ $response = $es->ml->set_upgrade_mode();
+
+The C<set_upgrade_mode()> method sets a cluster wide C<upgrade_mode> setting that prepares
+machine learning indices for an upgrade.
+
+See the L<set_upgrade_mode docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-set-upgrade-mode.html>
+for more information.
+
+Query string parameters:
+ C<enabled>,
+ C<error_trace>,
+ C<human>,
+ C<timeout>
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Migration.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Migration.pm
new file mode 100644
index 0000000..8a71168
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Migration.pm
@@ -0,0 +1,102 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Migration;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('migration');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Migration API for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->migration->deprecations();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<migration>
+namespace, to support the API
+L<Migration APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api.html>.
+
+=head1 METHODS
+
+The full documentation for the Migration APIs is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api.html>
+
+=head2 C<deprecations()>
+
+ $response = $es->migration->deprecations(
+ index => $index # optional
+ )
+
+The C<deprecations()> API is to be used to retrieve information about different cluster, node,
+and index level settings that use deprecated features that will be removed or changed in the
+next major version.
+
+See the L<deprecations docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-deprecation.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_assistance()>
+
+ $response = $es->migration->get_assistance(
+ index => $index | \@indices # optional
+ )
+
+The C<get_assistance()> API analyzes existing indices in the cluster and returns the information
+about indices that require some changes before the cluster can be upgraded to the next major version.
+
+See the L<get_assistance docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-assistance.html>
+for more information.
+
+Query string parameters:
+ C<allow_no_indices>,
+ C<error_trace>,
+ C<expand_wildcards>,
+ C<human>,
+ C<ignore_unavailable>
+
+=head2 C<upgrade()>
+
+ $response = $es->migration->upgrade(
+ index => $index # required
+ )
+
+The C<upgrade()> API performs the upgrade of internal indices to make them compatible with the
+next major version.
+
+See the L<upgrade() docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-upgrade.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<wait_for_completion>
+
+
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Monitoring.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Monitoring.pm
new file mode 100644
index 0000000..9e01fed
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Monitoring.pm
@@ -0,0 +1,35 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Monitoring;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('monitoring');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Monitoring for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->monitoring( body => {...} )
\ No newline at end of file
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Nodes.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Nodes.pm
new file mode 100644
index 0000000..4869589
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Nodes.pm
@@ -0,0 +1,203 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Nodes;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+__PACKAGE__->_install_api('nodes');
+
+1;
+
+__END__
+
+# ABSTRACT: A client for running node-level requests
+
+=head1 DESCRIPTION
+
+This module provides methods to make node-level requests, such as
+retrieving node info and stats.
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 METHODS
+
+
+=head2 C<info()>
+
+ $response = $e->nodes->info(
+ node_id => $node_id | \@node_ids # optional
+ metric => $metric | \@metrics # optional
+ );
+
+The C<info()> method returns static information about the nodes in the
+cluster, such as the configured maximum number of file handles, the maximum
+configured heap size or the threadpool settings.
+
+Allowed metrics are:
+ C<http>,
+ C<jvm>,
+ C<network>,
+ C<os>,
+ C<plugin>,
+ C<process>,
+ C<settings>,
+ C<thread_pool>,
+ C<timeout>,
+ C<transport>
+
+Query string parameters:
+ C<error_trace>,
+ C<flat_settings>,
+ C<human>
+
+See the L<node_info docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-info.html>
+for more information.
+
+=head2 C<stats()>
+
+ $response = $e->nodes->stats(
+ node_id => $node_id | \@node_ids # optional
+ metric => $metric | \@metrics # optional
+ index_metric => $ind_metric | \@ind_metrics # optional
+ );
+
+The C<stats()> method returns statistics about the nodes in the
+cluster, such as the number of currently open file handles, the current
+heap memory usage or the current number of threads in use.
+
+Stats can be returned for all nodes, or limited to particular nodes
+with the C<node_id> parameter. By default all metrics are returned, but
+these can be limited to those specified in the C<metric> parameter.
+
+Allowed metrics are:
+ C<_all>,
+ C<breaker>,
+ C<fs>,
+ C<http>,
+ C<include_segment_file_sizes>,
+ C<indices>,
+ C<jvm>,
+ C<network>,
+ C<os>,
+ C<process>,
+ C<thread_pool>,
+ C<timeout>,
+ C<transport>
+
+If the C<indices> metric (or C<_all>) is specified, then
+L<indices_stats|Search::Elasticsearch::Client::8_0::Direct::Indices/indices_stats()>
+information is returned on a per-node basis. Which indices stats are
+returned can be controlled with the C<index_metric> parameter:
+
+ $response = $e->nodes->stats(
+ node_id => 'node_1',
+ metric => ['indices','fs']
+ index_metric => ['docs','fielddata']
+ );
+
+Allowed index metrics are:
+ C<_all>,
+ C<completion>
+ C<docs>,
+ C<fielddata>,
+ C<filter_cache>,
+ C<flush>,
+ C<get>,
+ C<id_cache>,
+ C<indexing>,
+ C<merge>,
+ C<percolate>,
+ C<query_cache>,
+ C<refresh>,
+ C<search>,
+ C<segments>,
+ C<store>,
+ C<warmer>
+
+
+Query string parameters:
+ C<completion_fields>,
+ C<error_trace>,
+ C<fielddata_fields>,
+ C<fields>,
+ C<groups>,
+ C<human>,
+ C<level>,
+ C<types>
+
+See the L<stats docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html>
+for more information.
+
+=head2 C<hot_threads()>
+
+ $response = $e->nodes->hot_threads(
+ node_id => $node_id | \@node_ids # optional
+ )
+
+The C<hot_threads()> method is a useful tool for diagnosing busy nodes. It
+takes a snapshot of which threads are consuming the most CPU.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<ignore_idle_threads>,
+ C<interval>,
+ C<snapshots>,
+ C<threads>,
+ C<timeout>,
+ C<type>
+
+See the L<hot_threads docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-hot-threads.html>
+for more information.
+
+=head2 C<reload_secure_settings()>
+
+ $response = $e->nodes->reload_secure_settings(
+ node_id => $node_id | \@node_ids # optional
+ );
+
+The C<reload_secure_settings()> API will reload the reloadable settings stored in the keystore
+on each node.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<timeout>
+
+See the L<reload-secure-settings docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/secure-settings.html>
+for more information.
+
+=head2 C<usage()>
+
+ $response = $e->nodes->usage(
+ node_id => $node_id | \@node_ids # optional
+ metric => $metric | \@metrics # optional
+ )
+
+The C<usage()> API retrieve sinformation on the usage of features for each node.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<timeout>
+
+See the L<nodes_usage docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-usage.html>
+for more information.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Rollup.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Rollup.pm
new file mode 100644
index 0000000..98463e2
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Rollup.pm
@@ -0,0 +1,186 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Rollup;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('rollup');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Rollups for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->rollup->search( body => {...} )
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<rollup>
+namespace, to support the
+L<Rollup APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-apis.html>.
+
+The full documentation for the Rollups feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-rollup.html>
+
+
+=head1 GENERAL METHODS
+
+=head2 C<search()>
+
+ $response = $es->rollup->search(
+ index => $index | \@indices, # optional
+ body => {...} # optional
+ )
+
+The C<search()> method executes a normal search but can join the results from ordinary indices with
+those from rolled up indices.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<typed_keys>
+
+See the L<rollup search docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-search.html>
+for more information.
+
+=head1 JOB METHODS
+
+=head2 C<put_job()>
+
+ $response = $es->rollup->put_job(
+ id => $id, # required
+ body => {...} # optional
+ )
+
+The C<put_job()> method creates a rollup job which will rollup matching indices to a rolled up index
+in the background.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup create job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-put-job.html>
+for more information.
+
+=head2 C<delete_job()>
+
+ $response = $es->rollup->delete_job(
+ id => $id, # required
+ )
+
+The C<delete_job()> method deletes a rollup job by ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup delete job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-delete-job.html>
+for more information.
+
+=head2 C<get_jobs()>
+
+ $response = $es->rollup->get_jobs(
+ id => $id, # optional
+ )
+
+The C<get_job()> method retrieves a rollup job by ID, or all jobs if not specified.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup get jobs docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-get-job.html>
+for more information.
+
+=head2 C<start_job()>
+
+ $response = $es->rollup->start_job(
+ id => $id, # required
+ )
+
+The C<start_job()> method starts the specified rollup job.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup start job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-start-job.html>
+for more information.
+
+=head2 C<stop_job()>
+
+ $response = $es->rollup->stop_job(
+ id => $id, # required
+ )
+
+The C<stop_job()> method stops the specified rollup job.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<rollup stop job docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-stop-job.html>
+for more information.
+
+=head1 DATA METHODS
+
+=head2 C<get_rollup_caps()>
+
+ $response = $es->rollup->get_rollup_caps(
+ id => $index # optional
+ )
+
+The C<get_rollup_caps()> method returns the capabilities of any rollup jobs that have been configured for a specific index or index pattern.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<get rollup caps docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-get-rollup-caps.html>
+for more information.
+
+=head2 C<get_rollup_index_caps()>
+
+ $response = $es->rollup->get_rollup_index_caps(
+ id => $index # optional
+ )
+
+The C<get_rollup_index_caps()> method returns the rollup capabilities of all jobs inside of a rollup index.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<get rollup index caps docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-get-rollup-index-caps.html>
+for more information.
+
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/SQL.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/SQL.pm
new file mode 100644
index 0000000..43c0440
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/SQL.pm
@@ -0,0 +1,96 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::SQL;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('sql');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing SQL for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->sql->query( body => {...} )
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with an C<sql>
+namespace, to support the
+L<SQL APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest.html>.
+
+The full documentation for the SQL feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-sql.html>
+
+=head1 GENERAL METHODS
+
+=head2 C<query()>
+
+ $response = $es->sql->query(
+ body => {...} # required
+ )
+
+The C<query()> method executes an SQL query and returns the results.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<format>,
+ C<human>
+
+See the L<query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest.html>
+for more information.
+
+=head2 C<translate()>
+
+ $response = $es->sql->translate(
+ body => {...} # required
+ )
+
+The C<translate()> method takes an SQL query and returns the query DSL which would be executed.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<translate docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-translate.html>
+for more information.
+
+=head2 C<clear_cursor()>
+
+ $response = $es->sql->clear_cursor(
+ body => {...} # required
+ )
+
+The C<clear_cursor()> method cleans up an ongoing scroll request.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<query docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest.html>
+for more information.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/SSL.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/SSL.pm
new file mode 100644
index 0000000..b0b768c
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/SSL.pm
@@ -0,0 +1,49 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::SSL;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('ssl');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing SSL for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->ssl->certificates()
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with an C<ssl>
+namespace, to support the
+L<SSL APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-ssl.html>.
+
+=head1 GENERAL METHODS
+
+=head2 C<certificates()>
+
+ $response = $es->ssl->certificates()
+
+The C<certificates()> method returns all the certificate information on a single node of Elasticsearch.
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/SearchableSnapshots.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/SearchableSnapshots.pm
new file mode 100644
index 0000000..bd9b628
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/SearchableSnapshots.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::SearchableSnapshots;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('searchable_snapshots');
+
+1;
+
+__END__
+
+# ABSTRACT: Searchable Snapshots feature of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Searchable Snapshots feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/searchable-snapshots-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->searchable_snapshots->repository_stats(
+ 'repository' => $repository
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Security.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Security.pm
new file mode 100644
index 0000000..3bfb525
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Security.pm
@@ -0,0 +1,461 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Security;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('security');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Security API for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->security->authenticate();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<security>
+namespace, to support the
+L<Security APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api.html>.
+
+The full documentation for the Security feature is available here:
+L<https://www.elastic.co/guide/en/x-pack/current/xpack-security.html>
+
+=head1 GENERAL METHODS
+
+=head2 C<authenticate()>
+
+ $response = $es->security->authenticate()
+
+The C<authenticate()> method checks that the C<userinfo> is correct and returns
+a list of which roles are assigned to the user.
+
+See the L<authenticate docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-authenticate.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<clear_cached_realms()>
+
+ $response = $es->security->clear_cached_realms(
+ realms => $realms # required (comma-separated string)
+ );
+
+The C<clear_cached_realms()> method clears the caches for the specified realms
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<usernames>
+
+See the L<clear_cached_realms docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-cache.html>
+for more information.
+
+
+=head1 USER METHODS
+
+=head2 C<put_user()>
+
+ $response = $es->security->put_user(
+ username => $username, # required
+ body => {...} # required
+ );
+
+The C<put_user()> method creates a new user or updates an existing user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_user()>
+
+ $response = $es->security->get_user(
+ username => $username | \@usernames # optional
+ );
+
+The C<get_user()> method retrieves info for the specified users (or all users).
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_user()>
+
+ $response = $es->security->delete_user(
+ username => $username # required
+ );
+
+The C<delete_user()> method deletes the specified user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<change_password()>
+
+ $response = $es->security->change_password(
+ username => $username # optional
+ body => {
+ password => $password # required
+ }
+ )
+
+The C<change_password()> method changes the password for the specified user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+=head2 C<disable_user()>
+
+ $response = $es->security->disable_user(
+ username => $username # required
+ );
+
+The C<disable_user()> method disables the specified user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<enable_user()>
+
+ $response = $es->security->enable_user(
+ username => $username # required
+ );
+
+The C<enable_user()> method enables the specified user.
+
+See the L<User Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 ROLE METHODS
+
+=head2 C<put_role()>
+
+ $response = $es->security->put_role(
+ name => $name, # required
+ body => {...} # required
+ );
+
+The C<put_role()> method creates a new role or updates an existing role.
+
+See the L<Role Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-roles.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_role()>
+
+ $response = $es->security->get_role(
+ name => $name | \@names # optional
+ );
+
+The C<get_role()> method retrieves info for the specified roles (or all roles).
+
+See the L<Role Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-roles.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_role()>
+
+ $response = $es->security->delete_role(
+ name => $name # required
+ );
+
+The C<delete_role()> method deletes the specified role.
+
+See the L<Role Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-roles.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<clear_cached_roles()>
+
+ $response = $es->security->clear_cached_roles(
+ names => $names # required (comma-separated string)
+ );
+
+The C<clear_cached_roles()> method clears the caches for the specified roles.
+
+See the L<Role Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-roles.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+
+=head1 ROLE MAPPING METHODS
+
+=head2 C<put_role_mapping()>
+
+ $response = $es->security->put_role_mapping(
+ name => $name, # required
+ body => {...} # required
+ );
+
+The C<put_role_mapping()> method creates a new role mapping or updates an existing role mapping.
+
+See the L<Role Mapping docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-role-mapping.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<get_role_mapping()>
+
+ $response = $es->security->get_role_mapping(
+ name => $name, # optional
+ );
+
+The C<get_role_mapping()> method retrieves one or more role mappings.
+
+See the L<Role Mapping docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-role-mapping.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_role_mapping()>
+
+ $response = $es->security->delete_role_mapping(
+ name => $name, # required
+ );
+
+The C<delete_role_mapping()> method deletes a role mapping.
+
+See the L<Role Mapping docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-role-mapping.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 TOKEN METHODS
+
+=head2 C<get_token()>
+
+ $response = $es->security->get_token(
+ body => {...} # required
+ );
+
+The C<get_token()> method enables you to create bearer tokens for access without
+requiring basic authentication.
+
+See the L<Token Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-tokens.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<invalidate_token()>
+
+ $response = $es->security->invalidate_token(
+ body => {...} # required
+ );
+
+The C<invalidate_token()> method enables you to invalidate bearer tokens for access without
+requiring basic authentication.
+
+See the L<Token Management docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-tokens.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head1 API KEY METHODS
+
+=head2 C<create_api_key()>
+
+ $response = $es->security->create_api_key(
+ body => {...} # required
+ )
+
+The C<create_api_key()> API is used to create API keys which can be used for access instead
+of basic authentication.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<refresh>
+
+See the L<Create API Key docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html> for more.
+
+=head2 C<get_api_key()>
+
+ $response = $es->security->get_api_key(
+ id => $id, # optional
+ name => $name, # optional
+ realm_name => $realm, # optional
+ username => $username # optional
+ )
+
+The C<get_api_key()> API is used to get information about an API key.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<id>,
+ C<name>,
+ C<realm_name>,
+ C<username>
+
+See the L<Get API Key docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-api-key.html> for more.
+
+=head2 C<invalidate_api_key()>
+
+ $response = $es->security->invalidate_api_key(
+ id => $id, # optional
+ name => $name, # optional
+ realm_name => $realm, # optional
+ username => $username # optional
+ )
+
+The C<invalidate_api_key()> API is used to invalidate an API key.
+
+Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<id>,
+ C<name>,
+ C<realm_name>,
+ C<username>
+
+See the L<Invalidate API Key docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-invalidate-api-key.html> for more.
+
+=head1 USER PRIVILEGE METHODS
+
+=head2 C<get_user_privileges()>
+
+ $response = $es->get_user_privileges();
+
+ The C<get_user_privileges()> method retrieves the privileges granted to the current user.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+=head2 C<has_privileges()>
+ $response = $es->has_privileges(
+ user => $user, # optional
+ body => {...} # required
+ );
+
+ The C<has_privileges()> method checks whether the current or specified user has the listed privileges.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<Has Privileges docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-has-privileges.html> for more.
+
+
+=head1 APPLICATION PRIVILEGE METHODS
+
+=head2 C<put_privileges()>
+
+ $response = $es->put_privileges(
+ application => $application, # required
+ name => $name, # required
+ body => {...} # required
+ );
+
+ The C<put_privileges()> method creates or updates the named privilege for a particular application.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<refresh>
+
+See the L<Create or Update Application Privileges docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-privileges.html> for more.
+
+=head2 C<get_privileges()>
+
+ $response = $es->get_privileges(
+ application => $application, # required
+ name => $name, # required
+ );
+
+ The C<get_privileges()> method retrieves the named privilege for a particular application.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>
+
+See the L<Get Application Privileges docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-privileges.html> for more.
+
+=head2 C<delete_privileges()>
+
+ $response = $es->delete_privileges(
+ application => $application, # required
+ name => $name, # required
+ );
+
+ The C<delete_privileges()> method deletes the named privilege for a particular application.
+
+ Query string parameters:
+ C<error_trace>,
+ C<filter_path>,
+ C<human>,
+ C<refresh>
+
+See the L<Delete Application Privileges docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-privilege.html> for more.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Shutdown.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Shutdown.pm
new file mode 100644
index 0000000..7af39db
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Shutdown.pm
@@ -0,0 +1,43 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Shutdown;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('shutdown');
+
+1;
+
+__END__
+
+# ABSTRACT: Shutdown API of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Shutdown API is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/node-lifecycle-api.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->shutdown->get_node();
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Slm.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Slm.pm
new file mode 100644
index 0000000..3564440
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Slm.pm
@@ -0,0 +1,43 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Slm;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('slm');
+
+1;
+
+__END__
+
+# ABSTRACT: Snapshot lifecycle management feature of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Snapshot lifecycle management feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-lifecycle-management-api.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->slm->get_status();
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Snapshot.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Snapshot.pm
new file mode 100644
index 0000000..ae13ecd
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Snapshot.pm
@@ -0,0 +1,190 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Client::8_0::Direct::Snapshot;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+__PACKAGE__->_install_api('snapshot');
+
+1;
+
+__END__
+
+# ABSTRACT: A client for managing snapshot/restore
+
+=head1 DESCRIPTION
+
+This module provides methods to manage snapshot/restore, or backups.
+It can create, get and delete configured backup repositories, and
+create, get, delete and restore snapshots of your cluster or indices.
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 METHODS
+
+
+=head2 C<create_repository()>
+
+ $e->snapshot->create_repository(
+ repository => 'repository', # required
+ body => { defn } # required
+ );
+
+Create a repository for backups.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>,
+ C<verify>
+
+See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
+for more information.
+
+=head2 C<get_repository()>
+
+ $e->snapshot->get_repository(
+ repository => 'repository' | \@repositories # optional
+ );
+
+Retrieve existing repositories.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<local>,
+ C<master_timeout>
+
+See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
+for more information.
+
+=head2 C<verify_repository()>
+
+ $e->snapshot->verify_repository(
+ repository => 'repository' # required
+ );
+
+Verify existing repository.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
+for more information.
+
+=head2 C<delete_repository()>
+
+ $e->snapshot->delete_repository(
+ repository => 'repository' | \@repositories # required
+ );
+
+Delete repositories by name.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<timeout>
+
+See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
+for more information.
+
+=head2 C<create()>
+
+ $e->snapshot->create(
+ repository => 'repository', # required
+ snapshot => 'snapshot', # required,
+
+ body => { snapshot defn } # optional
+ );
+
+Create a snapshot of the whole cluster or individual indices in the named
+repository.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<wait_for_completion>
+
+=head2 C<get()>
+
+ $e->snapshot->get(
+ repository => 'repository' # required
+ snapshot => 'snapshot' | \@snapshots # required
+ );
+
+Retrieve snapshots in the named repository.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>,
+ C<verbose>
+
+See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
+for more information.
+
+=head2 C<delete()>
+
+ $e->snapshot->delete(
+ repository => 'repository', # required
+ snapshot => 'snapshot' # required
+ );
+
+Delete snapshot in the named repository.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
+
+See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
+for more information.
+
+
+=head2 C<restore()>
+
+ $e->snapshot->restore(
+ repository => 'repository', # required
+ snapshot => 'snapshot' # required
+
+ body => { what to restore } # optional
+ );
+
+Restore a named snapshot.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>,
+ C<wait_for_completion>
+
+See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
+for more information.
+
+=head2 C<status()>
+
+ $result = $e->snapshot->status(
+ repository => 'repository', # optional
+ snapshot => 'snapshot' | \@snapshots # optional
+ );
+
+Returns status information about the specified snapshots.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<ignore_unavailable>,
+ C<master_timeout>
+
+See the L<"snapshot/restore docs"|http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshot.html>
+for more information.
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Tasks.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Tasks.pm
new file mode 100644
index 0000000..e6f0b03
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Tasks.pm
@@ -0,0 +1,97 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Tasks;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+__PACKAGE__->_install_api('tasks');
+
+1;
+
+__END__
+
+# ABSTRACT: A client for accessing the Task Management API
+
+=head1 DESCRIPTION
+
+This module provides methods to access the Task Management API, such as listing
+tasks and cancelling tasks.
+
+It does L<Search::Elasticsearch::Role::Client::Direct>.
+
+=head1 METHODS
+
+=head2 C<list()>
+
+ $response = $e->tasks->list(
+ task_id => $task_id # optional
+ );
+
+The C<list()> method returns all running tasks or, if a C<task_id> is specified, info
+about that task.
+
+Query string parameters:
+ C<actions>,
+ C<detailed>,
+ C<error_trace>,
+ C<group_by>,
+ C<human>,
+ C<nodes>,
+ C<parent_task_id>,
+ C<timeout>,
+ C<wait_for_completion>
+
+See the L<task management docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
+for more information.
+
+=head2 C<get()>
+
+ $response = $e->tasks->get(
+ task_id => $task_id # required
+ );
+
+The C<get()> method returns the task with the specified ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<wait_for_completion>
+
+See the L<task management docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
+for more information.
+
+=head2 C<cancel()>
+
+ $response = $e->tasks->cancel(
+ task_id => $task_id # required
+ );
+
+The C<cancel()> method attempts to cancel the specified C<task_id> or multiple tasks.
+
+Query string parameters:
+ C<actions>,
+ C<error_trace>,
+ C<human>,
+ C<nodes>,
+ C<parent_task_id>,
+ C<timeout>
+
+See the L<task management docs|http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>
+for more information.
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Transform.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Transform.pm
new file mode 100644
index 0000000..ab0f643
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Transform.pm
@@ -0,0 +1,45 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Transform;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('transform');
+
+1;
+
+__END__
+
+# ABSTRACT: Transform feature of Search::Elasticsearch 8.x
+
+=head2 DESCRIPTION
+
+The full documentation for Transform feature is available here:
+L<https://www.elastic.co/guide/en/elasticsearch/reference/current/transform-apis.html>
+
+=head1 FOLLOW METHODS
+
+=head2 C<follow()>
+
+ my $response = $es->transform->get_transform(
+ 'transform_id' => $transform_id
+ );
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/Watcher.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/Watcher.pm
new file mode 100644
index 0000000..23e9d92
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/Watcher.pm
@@ -0,0 +1,225 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::Watcher;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('watcher');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing Watcher API for Search::Elasticsearch 8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->watcher->start();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<watcher>
+namespace, to support the
+L<Watcher APIs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api.html>.
+
+=head1 METHODS
+
+The full documentation for the Watcher feature is available here:
+L<https://www.elastic.co/guide/en/x-pack/current/xpack-alerting.html>
+
+=head2 C<put_watch()>
+
+ $response = $es->watcher->put_watch(
+ id => $watch_id, # required
+ body => {...}
+ );
+
+The C<put_watch()> method is used to register a new watcher or to update
+an existing watcher.
+
+See the L<put_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-put-watch.html>
+for more information.
+
+Query string parameters:
+ C<active>,
+ C<error_trace>,
+ C<human>,
+ C<if_primary_term>,
+ C<if_seq_no>,
+ C<master_timeout>,
+ C<version>
+
+=head2 C<get_watch()>
+
+ $response = $es->watcher->get_watch(
+ id => $watch_id, # required
+ );
+
+The C<get_watch()> method is used to retrieve a watch by ID.
+
+See the L<get_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-get-watch.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<delete_watch()>
+
+ $response = $es->watcher->delete_watch(
+ id => $watch_id, # required
+ );
+
+The C<delete_watch()> method is used to delete a watch by ID.
+
+Query string parameters:
+ C<error_trace>,
+ C<force>,
+ C<human>,
+ C<master_timeout>
+
+See the L<delete_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-delete-watch.html>
+for more information.
+
+=head2 C<execute_watch()>
+
+ $response = $es->watcher->execute_watch(
+ id => $watch_id, # optional
+ body => {...} # optional
+ );
+
+The C<execute_watch()> method forces the execution of a previously
+registered watch. Optional parameters may be passed in the C<body>.
+
+Query string parameters:
+ C<debug>,
+ C<error_trace>,
+ C<human>
+
+See the L<execute_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-execute-watch.html>
+for more information.
+
+=head2 C<ack_watch()>
+
+ $response = $es->watcher->ack_watch(
+ watch_id => $watch_id, # required
+ action_id => $action_id | \@action_ids # optional
+ );
+
+The C<ack_watch()> method is used to manually throttle the execution of
+a watch.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
+
+See the L<ack_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-ack-watch.html>
+for more information.
+
+=head2 C<activate_watch()>
+
+ $response = $es->watcher->activate_watch(
+ watch_id => $watch_id, # required
+ );
+
+The C<activate_watch()> method is used to activate a deactive watch.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
+
+See the L<activate_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-activate-watch.html>
+for more information.
+
+=head2 C<deactivate_watch()>
+
+ $response = $es->watcher->deactivate_watch(
+ watch_id => $watch_id, # required
+ );
+
+The C<deactivate_watch()> method is used to deactivate an active watch.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>,
+ C<master_timeout>
+
+See the L<deactivate_watch docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-deactivate-watch.html>
+for more information.
+
+=head2 C<stats()>
+
+ $response = $es->watcher->stats(
+ metric => $metric # optional
+ );
+
+The C<stats()> method returns information about the status of the watcher plugin.
+
+See the L<stats docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-stats.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<stop()>
+
+ $response = $es->watcher->stop();
+
+The C<stop()> method stops the watcher service if it is running.
+
+See the L<stop docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-stop.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<start()>
+
+ $response = $es->watcher->start();
+
+The C<start()> method starts the watcher service if it is not already running.
+
+See the L<start docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-start.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+=head2 C<restart()>
+
+ $response = $es->watcher->restart();
+
+The C<restart()> method stops then starts the watcher service.
+
+See the L<restart docs|https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-restart.html>
+for more information.
+
+Query string parameters:
+ C<error_trace>,
+ C<human>
+
+
+
diff --git a/lib/Search/Elasticsearch/Client/8_0/Direct/XPack.pm b/lib/Search/Elasticsearch/Client/8_0/Direct/XPack.pm
new file mode 100644
index 0000000..5ca4da3
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Direct/XPack.pm
@@ -0,0 +1,60 @@
+# 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 Search::Elasticsearch::Client::8_0::Direct::XPack;
+
+use Moo;
+with 'Search::Elasticsearch::Client::8_0::Role::API';
+with 'Search::Elasticsearch::Role::Client::Direct';
+use namespace::clean;
+
+__PACKAGE__->_install_api('xpack');
+
+1;
+
+__END__
+
+# ABSTRACT: Plugin providing XPack APIs for Search::Elasticsearch v8.x
+
+=head1 SYNOPSIS
+
+ my $response = $es->xpack->info();
+
+=head2 DESCRIPTION
+
+This class extends the L<Search::Elasticsearch> client with a C<xpack>
+namespace.
+
+=head1 METHODS
+
+=head2 C<info()>
+
+ my $response = $es->xpack->info();
+
+Provides general information about the installed X-Pack features.
+
+See the L<info|https://www.elastic.co/guide/en/elasticsearch/reference/current/info-api.html>
+for more information.
+
+=head2 C<usage()>
+
+ my $response = $es->xpack->usage();
+
+Provides usage information about the installed X-Pack features.
+
+See the L<usage|https://www.elastic.co/guide/en/elasticsearch/reference/current/usage-api.html>
+for more information.
\ No newline at end of file
diff --git a/lib/Search/Elasticsearch/Client/8_0/Role/API.pm b/lib/Search/Elasticsearch/Client/8_0/Role/API.pm
new file mode 100644
index 0000000..a32e575
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Role/API.pm
@@ -0,0 +1,7092 @@
+# 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 Search::Elasticsearch::Client::8_0::Role::API;
+
+use Moo::Role;
+with 'Search::Elasticsearch::Role::API';
+
+use Search::Elasticsearch::Util qw(throw);
+use namespace::clean;
+
+has 'api_version' => ( is => 'ro', default => '8_0' );
+
+our %API;
+
+#===================================
+sub api {
+#===================================
+ my $name = $_[1] || return \%API;
+ return $API{$name}
+ || throw( 'Internal', "Unknown api name ($name)" );
+}
+
+#===================================
+%API = (
+#===================================
+
+ 'bulk.metadata' => {
+ params => {
+ '_index' => '_index',
+ 'index' => '_index',
+ '_id' => '_id',
+ 'id' => '_id',
+ 'pipeline' => 'pipeline',
+ 'routing' => 'routing',
+ '_routing' => 'routing',
+ 'parent' => 'parent',
+ '_parent' => 'parent',
+ 'timestamp' => 'timestamp',
+ '_timestamp' => 'timestamp',
+ 'ttl' => 'ttl',
+ '_ttl' => 'ttl',
+ 'version' => 'version',
+ '_version' => 'version',
+ 'version_type' => 'version_type',
+ '_version_type' => 'version_type',
+ 'if_seq_no' => 'if_seq_no',
+ 'if_primary_term' => 'if_primary_term',
+ 'lang' => 'lang',
+ 'require_alias' => 'require_alias',
+ 'refresh' => 'refresh',
+ 'retry_on_conflict' => 'retru_on_conflict',
+ 'wait_for_active_shards' => 'wait_for_active_shards',
+ '_source' => '_source',
+ '_source_excludes' => '_source_excludes',
+ '_source_includes' => '_source_includes',
+ 'timeout' => 'timeout'
+ }
+ },
+ 'bulk.update' => {
+ params => [
+ '_source', '_source_includes',
+ '_source_excludes', 'detect_noop',
+ 'doc', 'doc_as_upsert',
+ 'fields', 'retry_on_conflict',
+ 'scripted_upsert', 'script',
+ 'upsert', 'lang',
+ 'params'
+ ]
+ },
+ 'bulk.required' => { params => ['index'] },
+
+#=== AUTOGEN - START ===
+
+ 'bulk' => {
+ body => { required => 1 },
+ doc => "docs-bulk",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_bulk" ], [ {}, "_bulk" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ pipeline => "string",
+ refresh => "enum",
+ require_alias => "boolean",
+ routing => "string",
+ timeout => "time",
+ type => "string",
+ wait_for_active_shards => "string",
+ },
+ serialize => "bulk",
+ },
+
+ 'clear_scroll' => {
+ body => {},
+ doc => "clear-scroll-api",
+ method => "DELETE",
+ parts => { scroll_id => { multi => 1 } },
+ paths => [
+ [ { scroll_id => 2 }, "_search", "scroll", "{scroll_id}" ],
+ [ {}, "_search", "scroll" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'close_point_in_time' => {
+ body => {},
+ doc => "point-in-time-api",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_pit" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'count' => {
+ body => {},
+ doc => "search-count",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_count" ], [ {}, "_count" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ min_score => "number",
+ preference => "string",
+ q => "string",
+ routing => "list",
+ terminate_after => "number",
+ },
+ },
+
+ 'create' => {
+ body => { required => 1 },
+ doc => "docs-index_",
+ method => "PUT",
+ parts => { id => {}, index => {} },
+ paths =>
+ [ [ { id => 2, index => 0 }, "{index}", "_create", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ pipeline => "string",
+ refresh => "enum",
+ routing => "string",
+ timeout => "time",
+ version => "number",
+ version_type => "enum",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'delete' => {
+ doc => "docs-delete",
+ method => "DELETE",
+ parts => { id => {}, index => {} },
+ paths => [ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ refresh => "enum",
+ routing => "string",
+ timeout => "time",
+ version => "number",
+ version_type => "enum",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'delete_by_query' => {
+ body => { required => 1 },
+ doc => "docs-delete-by-query",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_delete_by_query" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ conflicts => "enum",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ max_docs => "number",
+ preference => "string",
+ q => "string",
+ refresh => "boolean",
+ request_cache => "boolean",
+ requests_per_second => "number",
+ routing => "list",
+ scroll => "time",
+ scroll_size => "number",
+ search_timeout => "time",
+ search_type => "enum",
+ slices => "number|string",
+ sort => "list",
+ stats => "list",
+ terminate_after => "number",
+ timeout => "time",
+ version => "boolean",
+ wait_for_active_shards => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'delete_by_query_rethrottle' => {
+ doc => "docs-delete-by-query",
+ method => "POST",
+ parts => { task_id => {} },
+ paths => [
+ [ { task_id => 1 }, "_delete_by_query",
+ "{task_id}", "_rethrottle",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ requests_per_second => "number",
+ },
+ },
+
+ 'delete_script' => {
+ doc => "modules-scripting",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 1 }, "_scripts", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'exists' => {
+ doc => "docs-get",
+ method => "HEAD",
+ parts => { id => {}, index => {} },
+ paths => [ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ stored_fields => "list",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'exists_source' => {
+ doc => "docs-get",
+ method => "HEAD",
+ parts => { id => {}, index => {} },
+ paths =>
+ [ [ { id => 2, index => 0 }, "{index}", "_source", "{id}" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'explain' => {
+ body => {},
+ doc => "search-explain",
+ parts => { id => {}, index => {} },
+ paths =>
+ [ [ { id => 2, index => 0 }, "{index}", "_explain", "{id}" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ lenient => "boolean",
+ preference => "string",
+ q => "string",
+ routing => "string",
+ stored_fields => "list",
+ },
+ },
+
+ 'field_caps' => {
+ body => {},
+ doc => "search-field-caps",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_field_caps" ],
+ [ {}, "_field_caps" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ fields => "list",
+ filter_path => "list",
+ filters => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_unmapped => "boolean",
+ types => "list",
+ },
+ },
+
+ 'get' => {
+ doc => "docs-get",
+ parts => { id => {}, index => {} },
+ paths => [ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ force_synthetic_source => "boolean",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ stored_fields => "list",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'get_script' => {
+ doc => "modules-scripting",
+ parts => { id => {} },
+ paths => [ [ { id => 1 }, "_scripts", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'get_script_context' => {
+ doc => "painless-contexts",
+ parts => {},
+ paths => [ [ {}, "_script_context" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'get_script_languages' => {
+ doc => "modules-scripting",
+ parts => {},
+ paths => [ [ {}, "_script_language" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'get_source' => {
+ doc => "docs-get",
+ parts => { id => {}, index => {} },
+ paths =>
+ [ [ { id => 2, index => 0 }, "{index}", "_source", "{id}" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'index' => {
+ body => { required => 1 },
+ doc => "docs-index_",
+ method => "POST",
+ parts => { id => {}, index => {} },
+ paths => [
+ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ],
+ [ { index => 0 }, "{index}", "_doc" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ op_type => "enum",
+ pipeline => "string",
+ refresh => "enum",
+ require_alias => "boolean",
+ routing => "string",
+ timeout => "time",
+ version => "number",
+ version_type => "enum",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'info' => {
+ doc => "index",
+ parts => {},
+ paths => [ [ {} ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'knn_search' => {
+ body => {},
+ doc => "search-search",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_knn_search" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ routing => "list",
+ },
+ },
+
+ 'mget' => {
+ body => { required => 1 },
+ doc => "docs-multi-get",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_mget" ], [ {}, "_mget" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ force_synthetic_source => "boolean",
+ human => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ refresh => "boolean",
+ routing => "string",
+ stored_fields => "list",
+ },
+ },
+
+ 'msearch' => {
+ body => { required => 1 },
+ doc => "search-multi-search",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_msearch" ], [ {}, "_msearch" ] ],
+ qs => {
+ ccs_minimize_roundtrips => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ max_concurrent_searches => "number",
+ max_concurrent_shard_requests => "number",
+ pre_filter_shard_size => "number",
+ rest_total_hits_as_int => "boolean",
+ search_type => "enum",
+ typed_keys => "boolean",
+ },
+ serialize => "bulk",
+ },
+
+ 'msearch_template' => {
+ body => { required => 1 },
+ doc => "search-multi-search",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_msearch", "template" ],
+ [ {}, "_msearch", "template" ],
+ ],
+ qs => {
+ ccs_minimize_roundtrips => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ max_concurrent_searches => "number",
+ rest_total_hits_as_int => "boolean",
+ search_type => "enum",
+ typed_keys => "boolean",
+ },
+ serialize => "bulk",
+ },
+
+ 'mtermvectors' => {
+ body => {},
+ doc => "docs-multi-termvectors",
+ parts => { index => {} },
+ paths => [
+ [ { index => 0 }, "{index}", "_mtermvectors" ],
+ [ {}, "_mtermvectors" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ field_statistics => "boolean",
+ fields => "list",
+ filter_path => "list",
+ human => "boolean",
+ ids => "list",
+ offsets => "boolean",
+ payloads => "boolean",
+ positions => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ routing => "string",
+ term_statistics => "boolean",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'open_point_in_time' => {
+ doc => "point-in-time-api",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_pit" ] ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ keep_alive => "string",
+ preference => "string",
+ routing => "string",
+ },
+ },
+
+ 'ping' => {
+ doc => "index",
+ method => "HEAD",
+ parts => {},
+ paths => [ [ {} ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'put_script' => {
+ body => { required => 1 },
+ doc => "modules-scripting",
+ method => "PUT",
+ parts => { context => {}, id => {} },
+ paths => [
+ [ { context => 2, id => 1 }, "_scripts", "{id}", "{context}" ],
+ [ { id => 1 }, "_scripts", "{id}" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'rank_eval' => {
+ body => { required => 1 },
+ doc => "search-rank-eval",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_rank_eval" ],
+ [ {}, "_rank_eval" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ search_type => "enum",
+ },
+ },
+
+ 'reindex' => {
+ body => { required => 1 },
+ doc => "docs-reindex",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_reindex" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ max_docs => "number",
+ refresh => "boolean",
+ requests_per_second => "number",
+ scroll => "time",
+ slices => "number|string",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'reindex_rethrottle' => {
+ doc => "docs-reindex",
+ method => "POST",
+ parts => { task_id => {} },
+ paths =>
+ [ [ { task_id => 1 }, "_reindex", "{task_id}", "_rethrottle" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ requests_per_second => "number",
+ },
+ },
+
+ 'render_search_template' => {
+ body => {},
+ doc => "render-search-template-api",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_render", "template", "{id}" ],
+ [ {}, "_render", "template" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'scripts_painless_execute' => {
+ body => {},
+ doc => "painless-execute-api",
+ parts => {},
+ paths => [ [ {}, "_scripts", "painless", "_execute" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'scroll' => {
+ body => {},
+ doc => "",
+ parts => { scroll_id => {} },
+ paths => [
+ [ { scroll_id => 2 }, "_search", "scroll", "{scroll_id}" ],
+ [ {}, "_search", "scroll" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ rest_total_hits_as_int => "boolean",
+ scroll => "time",
+ },
+ },
+
+ 'search' => {
+ body => {},
+ doc => "search-search",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_search" ], [ {}, "_search" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ allow_no_indices => "boolean",
+ allow_partial_search_results => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ batched_reduce_size => "number",
+ ccs_minimize_roundtrips => "boolean",
+ default_operator => "enum",
+ df => "string",
+ docvalue_fields => "list",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ explain => "boolean",
+ filter_path => "list",
+ force_synthetic_source => "boolean",
+ from => "number",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ max_concurrent_shard_requests => "number",
+ min_compatible_shard_node => "string",
+ pre_filter_shard_size => "number",
+ preference => "string",
+ q => "string",
+ request_cache => "boolean",
+ rest_total_hits_as_int => "boolean",
+ routing => "list",
+ scroll => "time",
+ search_type => "enum",
+ seq_no_primary_term => "boolean",
+ size => "number",
+ sort => "list",
+ stats => "list",
+ stored_fields => "list",
+ suggest_field => "string",
+ suggest_mode => "enum",
+ suggest_size => "number",
+ suggest_text => "string",
+ terminate_after => "number",
+ timeout => "time",
+ track_scores => "boolean",
+ track_total_hits => "boolean|long",
+ typed_keys => "boolean",
+ version => "boolean",
+ },
+ },
+
+ 'search_mvt' => {
+ body => {},
+ doc => "search-vector-tile-api",
+ method => "POST",
+ parts => {
+ field => {},
+ index => { multi => 1 },
+ x => {},
+ y => {},
+ zoom => {}
+ },
+ paths => [
+ [ { field => 2, index => 0, x => 4, y => 5, zoom => 3 },
+ "{index}", "_mvt", "{field}", "{zoom}", "{x}", "{y}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ exact_bounds => "boolean",
+ extent => "int",
+ filter_path => "list",
+ grid_precision => "int",
+ grid_type => "enum",
+ human => "boolean",
+ size => "int",
+ track_total_hits => "boolean|long",
+ with_labels => "boolean",
+ },
+ },
+
+ 'search_shards' => {
+ doc => "search-shards",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_search_shards" ],
+ [ {}, "_search_shards" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ preference => "string",
+ routing => "string",
+ },
+ },
+
+ 'search_template' => {
+ body => { required => 1 },
+ doc => "search-template",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_search", "template" ],
+ [ {}, "_search", "template" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ ccs_minimize_roundtrips => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ explain => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ preference => "string",
+ profile => "boolean",
+ rest_total_hits_as_int => "boolean",
+ routing => "list",
+ scroll => "time",
+ search_type => "enum",
+ typed_keys => "boolean",
+ },
+ },
+
+ 'terms_enum' => {
+ body => {},
+ doc => "search-terms-enum",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_terms_enum" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'termvectors' => {
+ body => {},
+ doc => "docs-termvectors",
+ parts => { id => {}, index => {} },
+ paths => [
+ [ { id => 2, index => 0 }, "{index}", "_termvectors", "{id}" ],
+ [ { index => 0 }, "{index}", "_termvectors" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ field_statistics => "boolean",
+ fields => "list",
+ filter_path => "list",
+ human => "boolean",
+ offsets => "boolean",
+ payloads => "boolean",
+ positions => "boolean",
+ preference => "string",
+ realtime => "boolean",
+ routing => "string",
+ term_statistics => "boolean",
+ version => "number",
+ version_type => "enum",
+ },
+ },
+
+ 'update' => {
+ body => { required => 1 },
+ doc => "docs-update",
+ method => "POST",
+ parts => { id => {}, index => {} },
+ paths =>
+ [ [ { id => 2, index => 0 }, "{index}", "_update", "{id}" ] ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ lang => "string",
+ refresh => "enum",
+ require_alias => "boolean",
+ retry_on_conflict => "number",
+ routing => "string",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'update_by_query' => {
+ body => {},
+ doc => "docs-update-by-query",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_update_by_query" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ conflicts => "enum",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ max_docs => "number",
+ pipeline => "string",
+ preference => "string",
+ q => "string",
+ refresh => "boolean",
+ request_cache => "boolean",
+ requests_per_second => "number",
+ routing => "list",
+ scroll => "time",
+ scroll_size => "number",
+ search_timeout => "time",
+ search_type => "enum",
+ slices => "number|string",
+ sort => "list",
+ stats => "list",
+ terminate_after => "number",
+ timeout => "time",
+ version => "boolean",
+ version_type => "boolean",
+ wait_for_active_shards => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'update_by_query_rethrottle' => {
+ doc => "docs-update-by-query",
+ method => "POST",
+ parts => { task_id => {} },
+ paths => [
+ [ { task_id => 1 }, "_update_by_query",
+ "{task_id}", "_rethrottle",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ requests_per_second => "number",
+ },
+ },
+
+ 'async_search.delete' => {
+ doc => "async-search",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 1 }, "_async_search", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'async_search.get' => {
+ doc => "async-search",
+ parts => { id => {} },
+ paths => [ [ { id => 1 }, "_async_search", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ keep_alive => "time",
+ typed_keys => "boolean",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'async_search.status' => {
+ doc => "async-search",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_async_search", "status", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'async_search.submit' => {
+ body => {},
+ doc => "async-search",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_async_search" ],
+ [ {}, "_async_search" ],
+ ],
+ qs => {
+ _source => "list",
+ _source_excludes => "list",
+ _source_includes => "list",
+ allow_no_indices => "boolean",
+ allow_partial_search_results => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ batched_reduce_size => "number",
+ default_operator => "enum",
+ df => "string",
+ docvalue_fields => "list",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ explain => "boolean",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ keep_alive => "time",
+ keep_on_completion => "boolean",
+ lenient => "boolean",
+ max_concurrent_shard_requests => "number",
+ preference => "string",
+ q => "string",
+ request_cache => "boolean",
+ routing => "list",
+ search_type => "enum",
+ seq_no_primary_term => "boolean",
+ size => "number",
+ sort => "list",
+ stats => "list",
+ stored_fields => "list",
+ suggest_field => "string",
+ suggest_mode => "enum",
+ suggest_size => "number",
+ suggest_text => "string",
+ terminate_after => "number",
+ timeout => "time",
+ track_scores => "boolean",
+ track_total_hits => "boolean|long",
+ typed_keys => "boolean",
+ version => "boolean",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'autoscaling.delete_autoscaling_policy' => {
+ doc => "autoscaling-delete-autoscaling-policy",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_autoscaling", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'autoscaling.get_autoscaling_capacity' => {
+ doc => "autoscaling-get-autoscaling-capacity",
+ parts => {},
+ paths => [ [ {}, "_autoscaling", "capacity" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'autoscaling.get_autoscaling_policy' => {
+ doc => "autoscaling-get-autoscaling-policy",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_autoscaling", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'autoscaling.put_autoscaling_policy' => {
+ body => { required => 1 },
+ doc => "autoscaling-put-autoscaling-policy",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_autoscaling", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'cat.aliases' => {
+ doc => "cat-alias",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_cat", "aliases", "{name}" ],
+ [ {}, "_cat", "aliases" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.allocation' => {
+ doc => "cat-allocation",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 2 }, "_cat", "allocation", "{node_id}" ],
+ [ {}, "_cat", "allocation" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.component_templates' => {
+ doc => "cat-compoentn-templates",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_cat", "component_templates", "{name}" ],
+ [ {}, "_cat", "component_templates" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.count' => {
+ doc => "cat-count",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "count", "{index}" ],
+ [ {}, "_cat", "count" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.fielddata' => {
+ doc => "cat-fielddata",
+ parts => { fields => { multi => 1 } },
+ paths => [
+ [ { fields => 2 }, "_cat", "fielddata", "{fields}" ],
+ [ {}, "_cat", "fielddata" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.health' => {
+ doc => "cat-health",
+ parts => {},
+ paths => [ [ {}, "_cat", "health" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ ts => "boolean",
+ v => "boolean",
+ },
+ },
+
+ 'cat.help' => {
+ doc => "cat",
+ parts => {},
+ paths => [ [ {}, "_cat" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ },
+ },
+
+ 'cat.indices' => {
+ doc => "cat-indices",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "indices", "{index}" ],
+ [ {}, "_cat", "indices" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ health => "enum",
+ help => "boolean",
+ human => "boolean",
+ include_unloaded_segments => "boolean",
+ master_timeout => "time",
+ pri => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.master' => {
+ doc => "cat-master",
+ parts => {},
+ paths => [ [ {}, "_cat", "master" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.ml_data_frame_analytics' => {
+ doc => "cat-dfanalytics",
+ parts => { id => {} },
+ paths => [
+ [ { id => 4 }, "_cat", "ml", "data_frame", "analytics", "{id}" ],
+ [ {}, "_cat", "ml", "data_frame", "analytics" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.ml_datafeeds' => {
+ doc => "cat-datafeeds",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 3 }, "_cat",
+ "ml", "datafeeds",
+ "{datafeed_id}"
+ ],
+ [ {}, "_cat", "ml", "datafeeds" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.ml_jobs' => {
+ doc => "cat-anomaly-detectors",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 3 }, "_cat", "ml", "anomaly_detectors",
+ "{job_id}"
+ ],
+ [ {}, "_cat", "ml", "anomaly_detectors" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.ml_trained_models' => {
+ doc => "cat-trained-model",
+ parts => { model_id => {} },
+ paths => [
+ [ { model_id => 3 }, "_cat",
+ "ml", "trained_models",
+ "{model_id}"
+ ],
+ [ {}, "_cat", "ml", "trained_models" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ from => "int",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ size => "int",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.nodeattrs' => {
+ doc => "cat-nodeattrs",
+ parts => {},
+ paths => [ [ {}, "_cat", "nodeattrs" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.nodes' => {
+ doc => "cat-nodes",
+ parts => {},
+ paths => [ [ {}, "_cat", "nodes" ] ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ full_id => "boolean",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ include_unloaded_segments => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.pending_tasks' => {
+ doc => "cat-pending-tasks",
+ parts => {},
+ paths => [ [ {}, "_cat", "pending_tasks" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.plugins' => {
+ doc => "cat-plugins",
+ parts => {},
+ paths => [ [ {}, "_cat", "plugins" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ include_bootstrap => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.recovery' => {
+ doc => "cat-recovery",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "recovery", "{index}" ],
+ [ {}, "_cat", "recovery" ],
+ ],
+ qs => {
+ active_only => "boolean",
+ bytes => "enum",
+ detailed => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.repositories' => {
+ doc => "cat-repositories",
+ parts => {},
+ paths => [ [ {}, "_cat", "repositories" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.segments' => {
+ doc => "cat-segments",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "segments", "{index}" ],
+ [ {}, "_cat", "segments" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.shards' => {
+ doc => "cat-shards",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cat", "shards", "{index}" ],
+ [ {}, "_cat", "shards" ],
+ ],
+ qs => {
+ bytes => "enum",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.snapshots' => {
+ doc => "cat-snapshots",
+ parts => { repository => { multi => 1 } },
+ paths => [
+ [ { repository => 2 }, "_cat", "snapshots", "{repository}" ],
+ [ {}, "_cat", "snapshots" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.tasks' => {
+ doc => "tasks",
+ parts => {},
+ paths => [ [ {}, "_cat", "tasks" ] ],
+ qs => {
+ actions => "list",
+ detailed => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ nodes => "list",
+ parent_task_id => "string",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.templates' => {
+ doc => "cat-templates",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_cat", "templates", "{name}" ],
+ [ {}, "_cat", "templates" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ v => "boolean",
+ },
+ },
+
+ 'cat.thread_pool' => {
+ doc => "cat-thread-pool",
+ parts => { thread_pool_patterns => { multi => 1 } },
+ paths => [
+ [ { thread_pool_patterns => 2 }, "_cat",
+ "thread_pool", "{thread_pool_patterns}",
+ ],
+ [ {}, "_cat", "thread_pool" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ s => "list",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'cat.transforms' => {
+ doc => "cat-transforms",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 2 }, "_cat", "transforms", "{transform_id}" ],
+ [ {}, "_cat", "transforms" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ from => "int",
+ h => "list",
+ help => "boolean",
+ human => "boolean",
+ s => "list",
+ size => "int",
+ time => "enum",
+ v => "boolean",
+ },
+ },
+
+ 'ccr.delete_auto_follow_pattern' => {
+ doc => "ccr-delete-auto-follow-pattern",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_ccr", "auto_follow", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.follow' => {
+ body => { required => 1 },
+ doc => "ccr-put-follow",
+ method => "PUT",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "follow" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'ccr.follow_info' => {
+ doc => "ccr-get-follow-info",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "info" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.follow_stats' => {
+ doc => "ccr-get-follow-stats",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.forget_follower' => {
+ body => { required => 1 },
+ doc => "ccr-post-forget-follower",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "forget_follower" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.get_auto_follow_pattern' => {
+ doc => "ccr-get-auto-follow-pattern",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_ccr", "auto_follow", "{name}" ],
+ [ {}, "_ccr", "auto_follow" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.pause_auto_follow_pattern' => {
+ doc => "ccr-pause-auto-follow-pattern",
+ method => "POST",
+ parts => { name => {} },
+ paths =>
+ [ [ { name => 2 }, "_ccr", "auto_follow", "{name}", "pause" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.pause_follow' => {
+ doc => "ccr-post-pause-follow",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "pause_follow" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.put_auto_follow_pattern' => {
+ body => { required => 1 },
+ doc => "ccr-put-auto-follow-pattern",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_ccr", "auto_follow", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.resume_auto_follow_pattern' => {
+ doc => "ccr-resume-auto-follow-pattern",
+ method => "POST",
+ parts => { name => {} },
+ paths =>
+ [ [ { name => 2 }, "_ccr", "auto_follow", "{name}", "resume" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.resume_follow' => {
+ body => {},
+ doc => "ccr-post-resume-follow",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "resume_follow" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.stats' => {
+ doc => "ccr-get-stats",
+ parts => {},
+ paths => [ [ {}, "_ccr", "stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ccr.unfollow' => {
+ doc => "ccr-post-unfollow",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ccr", "unfollow" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'cluster.allocation_explain' => {
+ body => {},
+ doc => "cluster-allocation-explain",
+ parts => {},
+ paths => [ [ {}, "_cluster", "allocation", "explain" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ include_disk_info => "boolean",
+ include_yes_decisions => "boolean",
+ },
+ },
+
+ 'cluster.delete_component_template' => {
+ doc => "indices-component-template",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_component_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.delete_voting_config_exclusions' => {
+ doc => "voting-config-exclusions",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_cluster", "voting_config_exclusions" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ wait_for_removal => "boolean",
+ },
+ },
+
+ 'cluster.exists_component_template' => {
+ doc => "indices-component-template",
+ method => "HEAD",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_component_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'cluster.get_component_template' => {
+ doc => "indices-component-template",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 1 }, "_component_template", "{name}" ],
+ [ {}, "_component_template" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'cluster.get_settings' => {
+ doc => "cluster-get-settings",
+ parts => {},
+ paths => [ [ {}, "_cluster", "settings" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ include_defaults => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.health' => {
+ doc => "cluster-health",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 2 }, "_cluster", "health", "{index}" ],
+ [ {}, "_cluster", "health" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ level => "enum",
+ local => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ wait_for_events => "enum",
+ wait_for_no_initializing_shards => "boolean",
+ wait_for_no_relocating_shards => "boolean",
+ wait_for_nodes => "string",
+ wait_for_status => "enum",
+ },
+ },
+
+ 'cluster.pending_tasks' => {
+ doc => "cluster-pending",
+ parts => {},
+ paths => [ [ {}, "_cluster", "pending_tasks" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'cluster.post_voting_config_exclusions' => {
+ doc => "voting-config-exclusions",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_cluster", "voting_config_exclusions" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ node_ids => "string",
+ node_names => "string",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.put_component_template' => {
+ body => { required => 1 },
+ doc => "indices-component-template",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_component_template", "{name}" ] ],
+ qs => {
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.put_settings' => {
+ body => { required => 1 },
+ doc => "cluster-update-settings",
+ method => "PUT",
+ parts => {},
+ paths => [ [ {}, "_cluster", "settings" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.remote_info' => {
+ doc => "cluster-remote-info",
+ parts => {},
+ paths => [ [ {}, "_remote", "info" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'cluster.reroute' => {
+ body => {},
+ doc => "cluster-reroute",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_cluster", "reroute" ] ],
+ qs => {
+ dry_run => "boolean",
+ error_trace => "boolean",
+ explain => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ metric => "list",
+ retry_failed => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'cluster.state' => {
+ doc => "cluster-state",
+ parts => { index => { multi => 1 }, metric => { multi => 1 } },
+ paths => [
+ [ { index => 3, metric => 2 }, "_cluster",
+ "state", "{metric}",
+ "{index}",
+ ],
+ [ { metric => 2 }, "_cluster", "state", "{metric}" ],
+ [ {}, "_cluster", "state" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ wait_for_metadata_version => "number",
+ wait_for_timeout => "time",
+ },
+ },
+
+ 'cluster.stats' => {
+ doc => "cluster-stats",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 3 }, "_cluster", "stats", "nodes", "{node_id}" ],
+ [ {}, "_cluster", "stats" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'dangling_indices.delete_dangling_index' => {
+ doc => "modules-gateway-dangling-indices",
+ method => "DELETE",
+ parts => { index_uuid => {} },
+ paths => [ [ { index_uuid => 1 }, "_dangling", "{index_uuid}" ] ],
+ qs => {
+ accept_data_loss => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'dangling_indices.import_dangling_index' => {
+ doc => "modules-gateway-dangling-indices",
+ method => "POST",
+ parts => { index_uuid => {} },
+ paths => [ [ { index_uuid => 1 }, "_dangling", "{index_uuid}" ] ],
+ qs => {
+ accept_data_loss => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'dangling_indices.list_dangling_indices' => {
+ doc => "modules-gateway-dangling-indices",
+ parts => {},
+ paths => [ [ {}, "_dangling" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'enrich.delete_policy' => {
+ doc => "delete-enrich-policy-api",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_enrich", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'enrich.execute_policy' => {
+ doc => "execute-enrich-policy-api",
+ method => "PUT",
+ parts => { name => {} },
+ paths =>
+ [ [ { name => 2 }, "_enrich", "policy", "{name}", "_execute" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'enrich.get_policy' => {
+ doc => "get-enrich-policy-api",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_enrich", "policy", "{name}" ],
+ [ {}, "_enrich", "policy" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'enrich.put_policy' => {
+ body => { required => 1 },
+ doc => "put-enrich-policy-api",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_enrich", "policy", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'enrich.stats' => {
+ doc => "enrich-stats-api",
+ parts => {},
+ paths => [ [ {}, "_enrich", "_stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'eql.delete' => {
+ doc => "eql-search-api",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_eql", "search", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'eql.get' => {
+ doc => "eql-search-api",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_eql", "search", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ keep_alive => "time",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'eql.get_status' => {
+ doc => "eql-search-api",
+ parts => { id => {} },
+ paths => [ [ { id => 3 }, "_eql", "search", "status", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'eql.search' => {
+ body => { required => 1 },
+ doc => "eql-search-api",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_eql", "search" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ keep_alive => "time",
+ keep_on_completion => "boolean",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'features.get_features' => {
+ doc => "get-features-api",
+ parts => {},
+ paths => [ [ {}, "_features" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'features.reset_features' => {
+ doc => "modules-snapshots",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_features", "_reset" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'fleet.global_checkpoints' => {
+ doc => "get-global-checkpoints",
+ parts => { index => {} },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_fleet", "global_checkpoints" ] ],
+ qs => {
+ checkpoints => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ wait_for_advance => "boolean",
+ wait_for_index => "boolean",
+ },
+ },
+
+ 'fleet.msearch' => {
+ body => { required => 1 },
+ parts => { index => {} },
+ paths => [
+ [ { index => 0 }, "{index}", "_fleet", "_fleet_msearch" ],
+ [ {}, "_fleet", "_fleet_msearch" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ serialize => "bulk",
+ },
+
+ 'fleet.search' => {
+ body => {},
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_fleet", "_fleet_search" ] ],
+ qs => {
+ allow_partial_search_results => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ wait_for_checkpoints => "list",
+ wait_for_checkpoints_timeout => "time",
+ },
+ },
+
+ 'graph.explore' => {
+ body => {},
+ doc => "graph-explore-api",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_graph", "explore" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ routing => "string",
+ timeout => "time",
+ },
+ },
+
+ 'ilm.delete_lifecycle' => {
+ doc => "ilm-delete-lifecycle",
+ method => "DELETE",
+ parts => { policy => {} },
+ paths => [ [ { policy => 2 }, "_ilm", "policy", "{policy}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.explain_lifecycle' => {
+ doc => "ilm-explain-lifecycle",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ilm", "explain" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ only_errors => "boolean",
+ only_managed => "boolean",
+ },
+ },
+
+ 'ilm.get_lifecycle' => {
+ doc => "ilm-get-lifecycle",
+ parts => { policy => {} },
+ paths => [
+ [ { policy => 2 }, "_ilm", "policy", "{policy}" ],
+ [ {}, "_ilm", "policy" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.get_status' => {
+ doc => "ilm-get-status",
+ parts => {},
+ paths => [ [ {}, "_ilm", "status" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.migrate_to_data_tiers' => {
+ body => {},
+ doc => "ilm-migrate-to-data-tiers",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ilm", "migrate_to_data_tiers" ] ],
+ qs => {
+ dry_run => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ilm.move_to_step' => {
+ body => {},
+ doc => "ilm-move-to-step",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 2 }, "_ilm", "move", "{index}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.put_lifecycle' => {
+ body => {},
+ doc => "ilm-put-lifecycle",
+ method => "PUT",
+ parts => { policy => {} },
+ paths => [ [ { policy => 2 }, "_ilm", "policy", "{policy}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.remove_policy' => {
+ doc => "ilm-remove-policy",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ilm", "remove" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.retry' => {
+ doc => "ilm-retry-policy",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_ilm", "retry" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.start' => {
+ doc => "ilm-start",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ilm", "start" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ilm.stop' => {
+ doc => "ilm-stop",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ilm", "stop" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.add_block' => {
+ doc => "index-modules-blocks",
+ method => "PUT",
+ parts => { block => {}, index => { multi => 1 } },
+ paths => [
+ [ { block => 2, index => 0 }, "{index}", "_block", "{block}" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.analyze' => {
+ body => {},
+ doc => "indices-analyze",
+ parts => { index => {} },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_analyze" ], [ {}, "_analyze" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.clear_cache' => {
+ doc => "indices-clearcache",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_cache", "clear" ],
+ [ {}, "_cache", "clear" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ fielddata => "boolean",
+ fields => "list",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ query => "boolean",
+ request => "boolean",
+ },
+ },
+
+ 'indices.clone' => {
+ body => {},
+ doc => "indices-clone-index",
+ method => "PUT",
+ parts => { index => {}, target => {} },
+ paths => [
+ [ { index => 0, target => 2 }, "{index}", "_clone", "{target}" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.close' => {
+ doc => "indices-open-close",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_close" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.create' => {
+ body => {},
+ doc => "indices-create-index",
+ method => "PUT",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.create_data_stream' => {
+ doc => "data-streams",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_data_stream", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.data_streams_stats' => {
+ doc => "data-streams",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 1 }, "_data_stream", "{name}", "_stats" ],
+ [ {}, "_data_stream", "_stats" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.delete' => {
+ doc => "indices-delete-index",
+ method => "DELETE",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.delete_alias' => {
+ doc => "indices-aliases",
+ method => "DELETE",
+ parts => { index => { multi => 1 }, name => { multi => 1 } },
+ paths =>
+ [ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.delete_data_stream' => {
+ doc => "data-streams",
+ method => "DELETE",
+ parts => { name => { multi => 1 } },
+ paths => [ [ { name => 1 }, "_data_stream", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'indices.delete_index_template' => {
+ doc => "indices-templates",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_index_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.delete_template' => {
+ doc => "indices-templates",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.disk_usage' => {
+ doc => "indices-disk-usage",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_disk_usage" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flush => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ run_expensive_tasks => "boolean",
+ },
+ },
+
+ 'indices.downsample' => {
+ body => { required => 1 },
+ doc => "xpack-rollup",
+ method => "POST",
+ parts =>
+ { index => { required => 1 }, target_index => { required => 1 } },
+ paths => [
+ [ { index => 0, target_index => 2 }, "{index}",
+ "_downsample", "{target_index}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.exists' => {
+ doc => "indices-exists",
+ method => "HEAD",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_defaults => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.exists_alias' => {
+ doc => "indices-aliases",
+ method => "HEAD",
+ parts => { index => { multi => 1 }, name => { multi => 1 } },
+ paths => [
+ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ],
+ [ { name => 1 }, "_alias", "{name}" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.exists_index_template' => {
+ doc => "indices-templates",
+ method => "HEAD",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_index_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.exists_template' => {
+ doc => "indices-templates",
+ method => "HEAD",
+ parts => { name => { multi => 1 } },
+ paths => [ [ { name => 1 }, "_template", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.field_usage_stats' => {
+ doc => "field-usage-stats",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_field_usage_stats" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ fields => "list",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'indices.flush' => {
+ doc => "indices-flush",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_flush" ], [ {}, "_flush" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ wait_if_ongoing => "boolean",
+ },
+ },
+
+ 'indices.forcemerge' => {
+ doc => "indices-forcemerge",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_forcemerge" ],
+ [ {}, "_forcemerge" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flush => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ max_num_segments => "number",
+ only_expunge_deletes => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'indices.get' => {
+ doc => "indices-get-index",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ features => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_defaults => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_alias' => {
+ doc => "indices-aliases",
+ parts => { index => { multi => 1 }, name => { multi => 1 } },
+ paths => [
+ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ],
+ [ { index => 0 }, "{index}", "_alias" ],
+ [ { name => 1 }, "_alias", "{name}" ],
+ [ {}, "_alias" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.get_data_stream' => {
+ doc => "data-streams",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 1 }, "_data_stream", "{name}" ],
+ [ {}, "_data_stream" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'indices.get_field_mapping' => {
+ doc => "indices-get-field-mapping",
+ parts => { fields => { multi => 1 }, index => { multi => 1 } },
+ paths => [
+ [ { fields => 3, index => 0 }, "{index}",
+ "_mapping", "field",
+ "{fields}",
+ ],
+ [ { fields => 2 }, "_mapping", "field", "{fields}" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_defaults => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'indices.get_index_template' => {
+ doc => "indices-templates",
+ parts => { name => {} },
+ paths => [
+ [ { name => 1 }, "_index_template", "{name}" ],
+ [ {}, "_index_template" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_mapping' => {
+ doc => "indices-get-mapping",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_mapping" ], [ {}, "_mapping" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_settings' => {
+ doc => "indices-get-settings",
+ parts => { index => { multi => 1 }, name => { multi => 1 } },
+ paths => [
+ [ { index => 0, name => 2 }, "{index}", "_settings", "{name}" ],
+ [ { index => 0 }, "{index}", "_settings" ],
+ [ { name => 1 }, "_settings", "{name}" ],
+ [ {}, "_settings" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_defaults => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.get_template' => {
+ doc => "indices-templates",
+ parts => { name => { multi => 1 } },
+ paths =>
+ [ [ { name => 1 }, "_template", "{name}" ], [ {}, "_template" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.migrate_to_data_stream' => {
+ doc => "data-streams",
+ method => "POST",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_data_stream", "_migrate", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.modify_data_stream' => {
+ body => { required => 1 },
+ doc => "data-streams",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_data_stream", "_modify" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.open' => {
+ doc => "indices-open-close",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_open" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.promote_data_stream' => {
+ doc => "data-streams",
+ method => "POST",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_data_stream", "_promote", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'indices.put_alias' => {
+ body => {},
+ doc => "indices-aliases",
+ method => "PUT",
+ parts => { index => { multi => 1 }, name => {} },
+ paths =>
+ [ [ { index => 0, name => 2 }, "{index}", "_alias", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.put_index_template' => {
+ body => { required => 1 },
+ doc => "indices-templates",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_index_template", "{name}" ] ],
+ qs => {
+ cause => "string",
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.put_mapping' => {
+ body => { required => 1 },
+ doc => "indices-put-mapping",
+ method => "PUT",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_mapping" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ write_index_only => "boolean",
+ },
+ },
+
+ 'indices.put_settings' => {
+ body => { required => 1 },
+ doc => "indices-update-settings",
+ method => "PUT",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_settings" ],
+ [ {}, "_settings" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ preserve_existing => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'indices.put_template' => {
+ body => { required => 1 },
+ doc => "indices-templates",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 1 }, "_template", "{name}" ] ],
+ qs => {
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ order => "number",
+ },
+ },
+
+ 'indices.recovery' => {
+ doc => "indices-recovery",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_recovery" ],
+ [ {}, "_recovery" ]
+ ],
+ qs => {
+ active_only => "boolean",
+ detailed => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'indices.refresh' => {
+ doc => "indices-refresh",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_refresh" ], [ {}, "_refresh" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'indices.reload_search_analyzers' => {
+ doc => "indices-reload-analyzers",
+ parts => { index => { multi => 1 } },
+ paths =>
+ [ [ { index => 0 }, "{index}", "_reload_search_analyzers" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'indices.resolve_index' => {
+ doc => "indices-resolve-index-api",
+ parts => { name => { multi => 1 } },
+ paths => [ [ { name => 2 }, "_resolve", "index", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'indices.rollover' => {
+ body => {},
+ doc => "indices-rollover-index",
+ method => "POST",
+ parts => { alias => {}, new_index => {} },
+ paths => [
+ [ { alias => 0, new_index => 2 }, "{alias}",
+ "_rollover", "{new_index}",
+ ],
+ [ { alias => 0 }, "{alias}", "_rollover" ],
+ ],
+ qs => {
+ dry_run => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.segments' => {
+ doc => "indices-segments",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_segments" ],
+ [ {}, "_segments" ]
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ verbose => "boolean",
+ },
+ },
+
+ 'indices.shard_stores' => {
+ doc => "indices-shards-stores",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_shard_stores" ],
+ [ {}, "_shard_stores" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ status => "list",
+ },
+ },
+
+ 'indices.shrink' => {
+ body => {},
+ doc => "indices-shrink-index",
+ method => "PUT",
+ parts => { index => {}, target => {} },
+ paths => [
+ [ { index => 0, target => 2 }, "{index}", "_shrink", "{target}" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.simulate_index_template' => {
+ body => {},
+ doc => "indices-templates",
+ method => "POST",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_index_template", "_simulate_index", "{name}" ],
+ ],
+ qs => {
+ cause => "string",
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.simulate_template' => {
+ body => {},
+ doc => "indices-templates",
+ method => "POST",
+ parts => { name => {} },
+ paths => [
+ [ { name => 2 }, "_index_template", "_simulate", "{name}" ],
+ [ {}, "_index_template", "_simulate" ],
+ ],
+ qs => {
+ cause => "string",
+ create => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'indices.split' => {
+ body => {},
+ doc => "indices-split-index",
+ method => "PUT",
+ parts => { index => {}, target => {} },
+ paths => [
+ [ { index => 0, target => 2 }, "{index}", "_split", "{target}" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.stats' => {
+ doc => "indices-stats",
+ parts => { index => { multi => 1 }, metric => { multi => 1 } },
+ paths => [
+ [ { index => 0, metric => 2 }, "{index}", "_stats", "{metric}" ],
+ [ { index => 0 }, "{index}", "_stats" ],
+ [ { metric => 1 }, "_stats", "{metric}" ],
+ [ {}, "_stats" ],
+ ],
+ qs => {
+ completion_fields => "list",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ fielddata_fields => "list",
+ fields => "list",
+ filter_path => "list",
+ forbid_closed_indices => "boolean",
+ groups => "list",
+ human => "boolean",
+ include_segment_file_sizes => "boolean",
+ include_unloaded_segments => "boolean",
+ level => "enum",
+ },
+ },
+
+ 'indices.unfreeze' => {
+ doc => "unfreeze-index-api",
+ method => "POST",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_unfreeze" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ wait_for_active_shards => "string",
+ },
+ },
+
+ 'indices.update_aliases' => {
+ body => { required => 1 },
+ doc => "indices-aliases",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_aliases" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'indices.validate_query' => {
+ body => {},
+ doc => "search-validate",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_validate", "query" ],
+ [ {}, "_validate", "query" ],
+ ],
+ qs => {
+ all_shards => "boolean",
+ allow_no_indices => "boolean",
+ analyze_wildcard => "boolean",
+ analyzer => "string",
+ default_operator => "enum",
+ df => "string",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ explain => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ lenient => "boolean",
+ q => "string",
+ rewrite => "boolean",
+ },
+ },
+
+ 'ingest.delete_pipeline' => {
+ doc => "delete-pipeline-api",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_ingest", "pipeline", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'ingest.geo_ip_stats' => {
+ doc => "geoip-stats-api",
+ parts => {},
+ paths => [ [ {}, "_ingest", "geoip", "stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ingest.get_pipeline' => {
+ doc => "get-pipeline-api",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_ingest", "pipeline", "{id}" ],
+ [ {}, "_ingest", "pipeline" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ summary => "boolean",
+ },
+ },
+
+ 'ingest.processor_grok' => {
+ doc => "",
+ parts => {},
+ paths => [ [ {}, "_ingest", "processor", "grok" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ingest.put_pipeline' => {
+ body => { required => 1 },
+ doc => "put-pipeline-api",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_ingest", "pipeline", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_version => "int",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'ingest.simulate' => {
+ body => { required => 1 },
+ doc => "simulate-pipeline-api",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_ingest", "pipeline", "{id}", "_simulate" ],
+ [ {}, "_ingest", "pipeline", "_simulate" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ verbose => "boolean",
+ },
+ },
+
+ 'license.delete' => {
+ doc => "delete-license",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_license" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'license.get' => {
+ doc => "get-license",
+ parts => {},
+ paths => [ [ {}, "_license" ] ],
+ qs => {
+ accept_enterprise => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ },
+ },
+
+ 'license.get_basic_status' => {
+ doc => "get-basic-status",
+ parts => {},
+ paths => [ [ {}, "_license", "basic_status" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'license.get_trial_status' => {
+ doc => "get-trial-status",
+ parts => {},
+ paths => [ [ {}, "_license", "trial_status" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'license.post' => {
+ body => {},
+ doc => "update-license",
+ method => "PUT",
+ parts => {},
+ paths => [ [ {}, "_license" ] ],
+ qs => {
+ acknowledge => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'license.post_start_basic' => {
+ doc => "start-basic",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_license", "start_basic" ] ],
+ qs => {
+ acknowledge => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'license.post_start_trial' => {
+ doc => "start-trial",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_license", "start_trial" ] ],
+ qs => {
+ acknowledge => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ type => "string",
+ },
+ },
+
+ 'logstash.delete_pipeline' => {
+ doc => "logstash-api-delete-pipeline",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_logstash", "pipeline", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'logstash.get_pipeline' => {
+ doc => "logstash-api-get-pipeline",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_logstash", "pipeline", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'logstash.put_pipeline' => {
+ body => { required => 1 },
+ doc => "logstash-api-put-pipeline",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_logstash", "pipeline", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'migration.deprecations' => {
+ doc => "migration-api-deprecation",
+ parts => { index => {} },
+ paths => [
+ [ { index => 0 }, "{index}", "_migration", "deprecations" ],
+ [ {}, "_migration", "deprecations" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'migration.get_feature_upgrade_status' => {
+ doc => "migration-api-feature-upgrade",
+ parts => {},
+ paths => [ [ {}, "_migration", "system_features" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'migration.post_feature_upgrade' => {
+ doc => "migration-api-feature-upgrade",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_migration", "system_features" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.clear_trained_model_deployment_cache' => {
+ doc => "clear-trained-model-deployment-cache",
+ method => "POST",
+ parts => { model_id => { required => 1 } },
+ paths => [
+ [ { model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "deployment", "cache",
+ "_clear",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.close_job' => {
+ body => {},
+ doc => "ml-close-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_close",
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_calendar' => {
+ doc => "ml-delete-calendar",
+ method => "DELETE",
+ parts => { calendar_id => {} },
+ paths =>
+ [ [ { calendar_id => 2 }, "_ml", "calendars", "{calendar_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_calendar_event' => {
+ doc => "ml-delete-calendar-event",
+ method => "DELETE",
+ parts => { calendar_id => {}, event_id => {} },
+ paths => [
+ [ { calendar_id => 2, event_id => 4 }, "_ml",
+ "calendars", "{calendar_id}",
+ "events", "{event_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_calendar_job' => {
+ doc => "ml-delete-calendar-job",
+ method => "DELETE",
+ parts => { calendar_id => {}, job_id => {} },
+ paths => [
+ [ { calendar_id => 2, job_id => 4 },
+ "_ml", "calendars", "{calendar_id}", "jobs", "{job_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_data_frame_analytics' => {
+ doc => "delete-dfanalytics",
+ method => "DELETE",
+ parts => { id => {} },
+ paths =>
+ [ [ { id => 3 }, "_ml", "data_frame", "analytics", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_datafeed' => {
+ doc => "ml-delete-datafeed",
+ method => "DELETE",
+ parts => { datafeed_id => {} },
+ paths =>
+ [ [ { datafeed_id => 2 }, "_ml", "datafeeds", "{datafeed_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ },
+ },
+
+ 'ml.delete_expired_data' => {
+ body => {},
+ doc => "ml-delete-expired-data",
+ method => "DELETE",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml", "_delete_expired_data", "{job_id}" ],
+ [ {}, "_ml", "_delete_expired_data" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ requests_per_second => "number",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_filter' => {
+ doc => "ml-delete-filter",
+ method => "DELETE",
+ parts => { filter_id => {} },
+ paths => [ [ { filter_id => 2 }, "_ml", "filters", "{filter_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_forecast' => {
+ doc => "ml-delete-forecast",
+ method => "DELETE",
+ parts => { forecast_id => {}, job_id => {} },
+ paths => [
+ [ { forecast_id => 4, job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_forecast", "{forecast_id}",
+ ],
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_forecast",
+ ],
+ ],
+ qs => {
+ allow_no_forecasts => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_job' => {
+ doc => "ml-delete-job",
+ method => "DELETE",
+ parts => { job_id => {} },
+ paths =>
+ [ [ { job_id => 2 }, "_ml", "anomaly_detectors", "{job_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'ml.delete_model_snapshot' => {
+ doc => "ml-delete-snapshot",
+ method => "DELETE",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.delete_trained_model' => {
+ doc => "delete-trained-models",
+ method => "DELETE",
+ parts => { model_id => {} },
+ paths =>
+ [ [ { model_id => 2 }, "_ml", "trained_models", "{model_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.delete_trained_model_alias' => {
+ doc => "delete-trained-models-aliases",
+ method => "DELETE",
+ parts => { model_alias => {}, model_id => {} },
+ paths => [
+ [ { model_alias => 4, model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "model_aliases", "{model_alias}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.estimate_model_memory' => {
+ body => { required => 1 },
+ doc => "ml-apis",
+ method => "POST",
+ parts => {},
+ paths =>
+ [ [ {}, "_ml", "anomaly_detectors", "_estimate_model_memory" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.evaluate_data_frame' => {
+ body => { required => 1 },
+ doc => "evaluate-dfanalytics",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ml", "data_frame", "_evaluate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.explain_data_frame_analytics' => {
+ body => {},
+ doc => "explain-dfanalytics",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_explain"
+ ],
+ [ {}, "_ml", "data_frame", "analytics", "_explain" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.flush_job' => {
+ body => {},
+ doc => "ml-flush-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_flush",
+ ],
+ ],
+ qs => {
+ advance_time => "string",
+ calc_interim => "boolean",
+ end => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ skip_time => "string",
+ start => "string",
+ },
+ },
+
+ 'ml.forecast' => {
+ body => {},
+ doc => "ml-forecast",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_forecast",
+ ],
+ ],
+ qs => {
+ duration => "time",
+ error_trace => "boolean",
+ expires_in => "time",
+ filter_path => "list",
+ human => "boolean",
+ max_model_memory => "string",
+ },
+ },
+
+ 'ml.get_buckets' => {
+ body => {},
+ doc => "ml-get-bucket",
+ parts => { job_id => {}, timestamp => {} },
+ paths => [
+ [ { job_id => 2, timestamp => 5 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "buckets",
+ "{timestamp}",
+ ],
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "buckets",
+ ],
+ ],
+ qs => {
+ anomaly_score => "double",
+ desc => "boolean",
+ end => "string",
+ error_trace => "boolean",
+ exclude_interim => "boolean",
+ expand => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ sort => "string",
+ start => "string",
+ },
+ },
+
+ 'ml.get_calendar_events' => {
+ doc => "ml-get-calendar-event",
+ parts => { calendar_id => {} },
+ paths => [
+ [ { calendar_id => 2 }, "_ml",
+ "calendars", "{calendar_id}",
+ "events",
+ ],
+ ],
+ qs => {
+ end => "time",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ job_id => "string",
+ size => "int",
+ start => "string",
+ },
+ },
+
+ 'ml.get_calendars' => {
+ body => {},
+ doc => "ml-get-calendar",
+ parts => { calendar_id => {} },
+ paths => [
+ [ { calendar_id => 2 }, "_ml", "calendars", "{calendar_id}" ],
+ [ {}, "_ml", "calendars" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'ml.get_categories' => {
+ body => {},
+ doc => "ml-get-category",
+ parts => { category_id => {}, job_id => {} },
+ paths => [
+ [ { category_id => 5, job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "categories",
+ "{category_id}",
+ ],
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "categories",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ partition_field_value => "string",
+ size => "int",
+ },
+ },
+
+ 'ml.get_data_frame_analytics' => {
+ doc => "get-dfanalytics",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics", "{id}" ],
+ [ {}, "_ml", "data_frame", "analytics" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'ml.get_data_frame_analytics_stats' => {
+ doc => "get-dfanalytics-stats",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_stats"
+ ],
+ [ {}, "_ml", "data_frame", "analytics", "_stats" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ verbose => "boolean",
+ },
+ },
+
+ 'ml.get_datafeed_stats' => {
+ doc => "ml-get-datafeed-stats",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_stats",
+ ],
+ [ {}, "_ml", "datafeeds", "_stats" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_datafeeds' => {
+ doc => "ml-get-datafeed",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml", "datafeeds", "{datafeed_id}" ],
+ [ {}, "_ml", "datafeeds" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_filters' => {
+ doc => "ml-get-filter",
+ parts => { filter_id => {} },
+ paths => [
+ [ { filter_id => 2 }, "_ml", "filters", "{filter_id}" ],
+ [ {}, "_ml", "filters" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'ml.get_influencers' => {
+ body => {},
+ doc => "ml-get-influencer",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "influencers",
+ ],
+ ],
+ qs => {
+ desc => "boolean",
+ end => "string",
+ error_trace => "boolean",
+ exclude_interim => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ influencer_score => "double",
+ size => "int",
+ sort => "string",
+ start => "string",
+ },
+ },
+
+ 'ml.get_job_stats' => {
+ doc => "ml-get-job-stats",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_stats",
+ ],
+ [ {}, "_ml", "anomaly_detectors", "_stats" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_jobs' => {
+ doc => "ml-get-job",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml", "anomaly_detectors", "{job_id}" ],
+ [ {}, "_ml", "anomaly_detectors" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_memory_stats' => {
+ doc => "get-ml-memory",
+ parts => { node_id => {} },
+ paths => [
+ [ { node_id => 2 }, "_ml", "memory", "{node_id}", "_stats" ],
+ [ {}, "_ml", "memory", "_stats" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'ml.get_model_snapshot_upgrade_stats' => {
+ doc => "ml-get-job-model-snapshot-upgrade-stats",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ "_upgrade", "_stats",
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.get_model_snapshots' => {
+ body => {},
+ doc => "ml-get-snapshot",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ ],
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots",
+ ],
+ ],
+ qs => {
+ desc => "boolean",
+ end => "time",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ sort => "string",
+ start => "time",
+ },
+ },
+
+ 'ml.get_overall_buckets' => {
+ body => {},
+ doc => "ml-get-overall-buckets",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "overall_buckets",
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ bucket_span => "string",
+ end => "string",
+ error_trace => "boolean",
+ exclude_interim => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ overall_score => "double",
+ start => "string",
+ top_n => "int",
+ },
+ },
+
+ 'ml.get_records' => {
+ body => {},
+ doc => "ml-get-record",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "results", "records",
+ ],
+ ],
+ qs => {
+ desc => "boolean",
+ end => "string",
+ error_trace => "boolean",
+ exclude_interim => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ record_score => "double",
+ size => "int",
+ sort => "string",
+ start => "string",
+ },
+ },
+
+ 'ml.get_trained_models' => {
+ doc => "get-trained-models",
+ parts => { model_id => {} },
+ paths => [
+ [ { model_id => 2 }, "_ml", "trained_models", "{model_id}" ],
+ [ {}, "_ml", "trained_models" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ decompress_definition => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ include => "string",
+ include_model_definition => "boolean",
+ size => "int",
+ tags => "list",
+ },
+ },
+
+ 'ml.get_trained_models_stats' => {
+ doc => "get-trained-models-stats",
+ parts => { model_id => {} },
+ paths => [
+ [ { model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "_stats",
+ ],
+ [ {}, "_ml", "trained_models", "_stats" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'ml.infer_trained_model' => {
+ body => { required => 1 },
+ doc => "infer-trained-model",
+ method => "POST",
+ parts => { model_id => { required => 1 } },
+ paths => [
+ [ { model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "_infer",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.info' => {
+ doc => "get-ml-info",
+ parts => {},
+ paths => [ [ {}, "_ml", "info" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.open_job' => {
+ body => {},
+ doc => "ml-open-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_open"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.post_calendar_events' => {
+ body => { required => 1 },
+ doc => "ml-post-calendar-event",
+ method => "POST",
+ parts => { calendar_id => {} },
+ paths => [
+ [ { calendar_id => 2 }, "_ml",
+ "calendars", "{calendar_id}",
+ "events",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.post_data' => {
+ body => { required => 1 },
+ doc => "ml-post-data",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_data"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ reset_end => "string",
+ reset_start => "string",
+ },
+ serialize => "bulk",
+ },
+
+ 'ml.preview_data_frame_analytics' => {
+ body => {},
+ doc => "preview-dfanalytics",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_preview"
+ ],
+ [ {}, "_ml", "data_frame", "analytics", "_preview" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.preview_datafeed' => {
+ body => {},
+ doc => "ml-preview-datafeed",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_preview",
+ ],
+ [ {}, "_ml", "datafeeds", "_preview" ],
+ ],
+ qs => {
+ end => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ start => "string",
+ },
+ },
+
+ 'ml.put_calendar' => {
+ body => {},
+ doc => "ml-put-calendar",
+ method => "PUT",
+ parts => { calendar_id => {} },
+ paths =>
+ [ [ { calendar_id => 2 }, "_ml", "calendars", "{calendar_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_calendar_job' => {
+ doc => "ml-put-calendar-job",
+ method => "PUT",
+ parts => { calendar_id => {}, job_id => {} },
+ paths => [
+ [ { calendar_id => 2, job_id => 4 },
+ "_ml", "calendars", "{calendar_id}", "jobs", "{job_id}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_data_frame_analytics' => {
+ body => { required => 1 },
+ doc => "put-dfanalytics",
+ method => "PUT",
+ parts => { id => {} },
+ paths =>
+ [ [ { id => 3 }, "_ml", "data_frame", "analytics", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_datafeed' => {
+ body => { required => 1 },
+ doc => "ml-put-datafeed",
+ method => "PUT",
+ parts => { datafeed_id => {} },
+ paths =>
+ [ [ { datafeed_id => 2 }, "_ml", "datafeeds", "{datafeed_id}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'ml.put_filter' => {
+ body => { required => 1 },
+ doc => "ml-put-filter",
+ method => "PUT",
+ parts => { filter_id => {} },
+ paths => [ [ { filter_id => 2 }, "_ml", "filters", "{filter_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_job' => {
+ body => { required => 1 },
+ doc => "ml-put-job",
+ method => "PUT",
+ parts => { job_id => {} },
+ paths =>
+ [ [ { job_id => 2 }, "_ml", "anomaly_detectors", "{job_id}" ] ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'ml.put_trained_model' => {
+ body => { required => 1 },
+ doc => "put-trained-models",
+ method => "PUT",
+ parts => { model_id => {} },
+ paths =>
+ [ [ { model_id => 2 }, "_ml", "trained_models", "{model_id}" ] ],
+ qs => {
+ defer_definition_decompression => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.put_trained_model_alias' => {
+ doc => "put-trained-models-aliases",
+ method => "PUT",
+ parts => { model_alias => {}, model_id => {} },
+ paths => [
+ [ { model_alias => 4, model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "model_aliases", "{model_alias}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ reassign => "boolean",
+ },
+ },
+
+ 'ml.put_trained_model_definition_part' => {
+ body => { required => 1 },
+ doc => "put-trained-model-definition-part",
+ method => "PUT",
+ parts => { model_id => {}, part => {} },
+ paths => [
+ [ { model_id => 2, part => 4 }, "_ml",
+ "trained_models", "{model_id}",
+ "definition", "{part}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.put_trained_model_vocabulary' => {
+ body => { required => 1 },
+ doc => "put-trained-model-vocabulary",
+ method => "PUT",
+ parts => { model_id => {} },
+ paths => [
+ [ { model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "vocabulary",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.reset_job' => {
+ doc => "ml-reset-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_reset",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'ml.revert_model_snapshot' => {
+ body => {},
+ doc => "ml-revert-snapshot",
+ method => "POST",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ "_revert",
+ ],
+ ],
+ qs => {
+ delete_intervening_results => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'ml.set_upgrade_mode' => {
+ doc => "ml-set-upgrade-mode",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ml", "set_upgrade_mode" ] ],
+ qs => {
+ enabled => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.start_data_frame_analytics' => {
+ body => {},
+ doc => "start-dfanalytics",
+ method => "POST",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_start"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.start_datafeed' => {
+ body => {},
+ doc => "ml-start-datafeed",
+ method => "POST",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_start",
+ ],
+ ],
+ qs => {
+ end => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ start => "string",
+ timeout => "time",
+ },
+ },
+
+ 'ml.start_trained_model_deployment' => {
+ doc => "start-trained-model-deployment",
+ method => "POST",
+ parts => { model_id => { required => 1 } },
+ paths => [
+ [ { model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "deployment", "_start",
+ ],
+ ],
+ qs => {
+ cache_size => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ number_of_allocations => "int",
+ queue_capacity => "int",
+ threads_per_allocation => "int",
+ timeout => "time",
+ wait_for => "string",
+ },
+ },
+
+ 'ml.stop_data_frame_analytics' => {
+ body => {},
+ doc => "stop-dfanalytics",
+ method => "POST",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics", "{id}",
+ "_stop"
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.stop_datafeed' => {
+ body => {},
+ doc => "ml-stop-datafeed",
+ method => "POST",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_stop",
+ ],
+ ],
+ qs => {
+ allow_no_datafeeds => "boolean",
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'ml.stop_trained_model_deployment' => {
+ body => {},
+ doc => "stop-trained-model-deployment",
+ method => "POST",
+ parts => { model_id => { required => 1 } },
+ paths => [
+ [ { model_id => 2 }, "_ml",
+ "trained_models", "{model_id}",
+ "deployment", "_stop",
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ },
+ },
+
+ 'ml.update_data_frame_analytics' => {
+ body => { required => 1 },
+ doc => "update-dfanalytics",
+ method => "POST",
+ parts => { id => {} },
+ paths => [
+ [ { id => 3 }, "_ml", "data_frame", "analytics",
+ "{id}", "_update"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.update_datafeed' => {
+ body => { required => 1 },
+ doc => "ml-update-datafeed",
+ method => "POST",
+ parts => { datafeed_id => {} },
+ paths => [
+ [ { datafeed_id => 2 }, "_ml",
+ "datafeeds", "{datafeed_id}",
+ "_update",
+ ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_throttled => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'ml.update_filter' => {
+ body => { required => 1 },
+ doc => "ml-update-filter",
+ method => "POST",
+ parts => { filter_id => {} },
+ paths => [
+ [ { filter_id => 2 }, "_ml", "filters", "{filter_id}",
+ "_update"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.update_job' => {
+ body => { required => 1 },
+ doc => "ml-update-job",
+ method => "POST",
+ parts => { job_id => {} },
+ paths => [
+ [ { job_id => 2 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "_update",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.update_model_snapshot' => {
+ body => { required => 1 },
+ doc => "ml-update-snapshot",
+ method => "POST",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ "_update",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.upgrade_job_snapshot' => {
+ doc => "ml-upgrade-job-model-snapshot",
+ method => "POST",
+ parts => { job_id => {}, snapshot_id => {} },
+ paths => [
+ [ { job_id => 2, snapshot_id => 4 }, "_ml",
+ "anomaly_detectors", "{job_id}",
+ "model_snapshots", "{snapshot_id}",
+ "_upgrade",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'ml.validate' => {
+ body => { required => 1 },
+ doc => "ml-jobs",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_ml", "anomaly_detectors", "_validate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ml.validate_detector' => {
+ body => { required => 1 },
+ doc => "ml-jobs",
+ method => "POST",
+ parts => {},
+ paths =>
+ [ [ {}, "_ml", "anomaly_detectors", "_validate", "detector" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'monitoring.bulk' => {
+ body => { required => 1 },
+ doc => "monitor-elasticsearch-cluster",
+ method => "POST",
+ parts => { type => {} },
+ paths => [
+ [ { type => 1 }, "_monitoring", "{type}", "bulk" ],
+ [ {}, "_monitoring", "bulk" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ interval => "string",
+ system_api_version => "string",
+ system_id => "string",
+ },
+ serialize => "bulk",
+ },
+
+ 'nodes.clear_repositories_metering_archive' => {
+ doc => "clear-repositories-metering-archive-api",
+ method => "DELETE",
+ parts => { max_archive_version => {}, node_id => { multi => 1 } },
+ paths => [
+ [ { max_archive_version => 3, node_id => 1 },
+ "_nodes", "{node_id}", "_repositories_metering",
+ "{max_archive_version}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'nodes.get_repositories_metering_info' => {
+ doc => "get-repositories-metering-api",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 1 }, "_nodes",
+ "{node_id}", "_repositories_metering",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'nodes.hot_threads' => {
+ doc => "cluster-nodes-hot-threads",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 1 }, "_nodes", "{node_id}", "hot_threads" ],
+ [ {}, "_nodes", "hot_threads" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_idle_threads => "boolean",
+ interval => "time",
+ snapshots => "number",
+ sort => "enum",
+ threads => "number",
+ timeout => "time",
+ type => "enum",
+ },
+ },
+
+ 'nodes.info' => {
+ doc => "cluster-nodes-info",
+ parts => { metric => { multi => 1 }, node_id => { multi => 1 } },
+ paths => [
+ [ { metric => 2, node_id => 1 }, "_nodes",
+ "{node_id}", "{metric}",
+ ],
+ [ { metric => 1 }, "_nodes", "{metric}" ],
+ [ { node_id => 1 }, "_nodes", "{node_id}" ],
+ [ {}, "_nodes" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ flat_settings => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'nodes.reload_secure_settings' => {
+ body => {},
+ doc => "",
+ method => "POST",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 1 }, "_nodes",
+ "{node_id}", "reload_secure_settings",
+ ],
+ [ {}, "_nodes", "reload_secure_settings" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'nodes.stats' => {
+ doc => "cluster-nodes-stats",
+ parts => {
+ index_metric => { multi => 1 },
+ metric => { multi => 1 },
+ node_id => { multi => 1 },
+ },
+ paths => [
+ [ { index_metric => 4, metric => 3, node_id => 1 },
+ "_nodes", "{node_id}", "stats", "{metric}", "{index_metric}",
+ ],
+ [ { index_metric => 3, metric => 2 }, "_nodes",
+ "stats", "{metric}",
+ "{index_metric}",
+ ],
+ [ { metric => 3, node_id => 1 }, "_nodes",
+ "{node_id}", "stats",
+ "{metric}",
+ ],
+ [ { metric => 2 }, "_nodes", "stats", "{metric}" ],
+ [ { node_id => 1 }, "_nodes", "{node_id}", "stats" ],
+ [ {}, "_nodes", "stats" ],
+ ],
+ qs => {
+ completion_fields => "list",
+ error_trace => "boolean",
+ fielddata_fields => "list",
+ fields => "list",
+ filter_path => "list",
+ groups => "boolean",
+ human => "boolean",
+ include_segment_file_sizes => "boolean",
+ include_unloaded_segments => "boolean",
+ level => "enum",
+ timeout => "time",
+ types => "list",
+ },
+ },
+
+ 'nodes.usage' => {
+ doc => "cluster-nodes-usage",
+ parts => { metric => { multi => 1 }, node_id => { multi => 1 } },
+ paths => [
+ [ { metric => 3, node_id => 1 }, "_nodes",
+ "{node_id}", "usage",
+ "{metric}",
+ ],
+ [ { metric => 2 }, "_nodes", "usage", "{metric}" ],
+ [ { node_id => 1 }, "_nodes", "{node_id}", "usage" ],
+ [ {}, "_nodes", "usage" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'rollup.delete_job' => {
+ doc => "rollup-delete-job",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_rollup", "job", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.get_jobs' => {
+ doc => "rollup-get-job",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_rollup", "job", "{id}" ],
+ [ {}, "_rollup", "job" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.get_rollup_caps' => {
+ doc => "rollup-get-rollup-caps",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_rollup", "data", "{id}" ],
+ [ {}, "_rollup", "data" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.get_rollup_index_caps' => {
+ doc => "rollup-get-rollup-index-caps",
+ parts => { index => {} },
+ paths => [ [ { index => 0 }, "{index}", "_rollup", "data" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.put_job' => {
+ body => { required => 1 },
+ doc => "rollup-put-job",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_rollup", "job", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.rollup_search' => {
+ body => { required => 1 },
+ doc => "rollup-search",
+ parts => { index => { multi => 1 } },
+ paths => [ [ { index => 0 }, "{index}", "_rollup_search" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ rest_total_hits_as_int => "boolean",
+ typed_keys => "boolean",
+ },
+ },
+
+ 'rollup.start_job' => {
+ doc => "rollup-start-job",
+ method => "POST",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_rollup", "job", "{id}", "_start" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'rollup.stop_job' => {
+ doc => "rollup-stop-job",
+ method => "POST",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_rollup", "job", "{id}", "_stop" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'searchable_snapshots.cache_stats' => {
+ doc => "searchable-snapshots-apis",
+ parts => { node_id => { multi => 1 } },
+ paths => [
+ [ { node_id => 1 }, "_searchable_snapshots",
+ "{node_id}", "cache",
+ "stats",
+ ],
+ [ {}, "_searchable_snapshots", "cache", "stats" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'searchable_snapshots.clear_cache' => {
+ doc => "searchable-snapshots-apis",
+ method => "POST",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}",
+ "_searchable_snapshots", "cache",
+ "clear",
+ ],
+ [ {}, "_searchable_snapshots", "cache", "clear" ],
+ ],
+ qs => {
+ allow_no_indices => "boolean",
+ error_trace => "boolean",
+ expand_wildcards => "enum",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ },
+ },
+
+ 'searchable_snapshots.mount' => {
+ body => { required => 1 },
+ doc => "searchable-snapshots-api-mount-snapshot",
+ method => "POST",
+ parts => { repository => {}, snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ "_mount",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ storage => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'searchable_snapshots.stats' => {
+ doc => "searchable-snapshots-apis",
+ parts => { index => { multi => 1 } },
+ paths => [
+ [ { index => 0 }, "{index}", "_searchable_snapshots", "stats" ],
+ [ {}, "_searchable_snapshots", "stats" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ level => "enum",
+ },
+ },
+
+ 'security.activate_user_profile' => {
+ body => { required => 1 },
+ doc => "security-api-activate-user-profile",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "profile", "_activate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.authenticate' => {
+ doc => "security-api-authenticate",
+ parts => {},
+ paths => [ [ {}, "_security", "_authenticate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.bulk_update_api_keys' => {
+ body => { required => 1 },
+ doc => "security-api-bulk-update-api-keys",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key", "_bulk_update" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.change_password' => {
+ body => { required => 1 },
+ doc => "security-api-change-password",
+ method => "PUT",
+ parts => { username => {} },
+ paths => [
+ [ { username => 2 }, "_security",
+ "user", "{username}",
+ "_password",
+ ],
+ [ {}, "_security", "user", "_password" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.clear_api_key_cache' => {
+ doc => "security-api-clear-api-key-cache",
+ method => "POST",
+ parts => { ids => { multi => 1 } },
+ paths => [
+ [ { ids => 2 }, "_security", "api_key", "{ids}", "_clear_cache" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.clear_cached_privileges' => {
+ doc => "security-api-clear-privilege-cache",
+ method => "POST",
+ parts => { application => { multi => 1 } },
+ paths => [
+ [ { application => 2 }, "_security",
+ "privilege", "{application}",
+ "_clear_cache",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.clear_cached_realms' => {
+ doc => "security-api-clear-cache",
+ method => "POST",
+ parts => { realms => { multi => 1 } },
+ paths => [
+ [ { realms => 2 }, "_security", "realm", "{realms}",
+ "_clear_cache",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ usernames => "list",
+ },
+ },
+
+ 'security.clear_cached_roles' => {
+ doc => "security-api-clear-role-cache",
+ method => "POST",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_security", "role", "{name}", "_clear_cache" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.clear_cached_service_tokens' => {
+ doc => "security-api-clear-service-token-caches",
+ method => "POST",
+ parts => { name => { multi => 1 }, namespace => {}, service => {} },
+ paths => [
+ [ { name => 6, namespace => 2, service => 3 }, "_security",
+ "service", "{namespace}",
+ "{service}", "credential",
+ "token", "{name}",
+ "_clear_cache",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.create_api_key' => {
+ body => { required => 1 },
+ doc => "security-api-create-api-key",
+ method => "PUT",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.create_service_token' => {
+ doc => "security-api-create-service-token",
+ method => "POST",
+ parts => { name => {}, namespace => {}, service => {} },
+ paths => [
+ [ { name => 6, namespace => 2, service => 3 }, "_security",
+ "service", "{namespace}",
+ "{service}", "credential",
+ "token", "{name}",
+ ],
+ [ { namespace => 2, service => 3 }, "_security",
+ "service", "{namespace}",
+ "{service}", "credential",
+ "token",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_privileges' => {
+ doc => "security-api-delete-privilege",
+ method => "DELETE",
+ parts => { application => {}, name => {} },
+ paths => [
+ [ { application => 2, name => 3 }, "_security",
+ "privilege", "{application}",
+ "{name}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_role' => {
+ doc => "security-api-delete-role",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_security", "role", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_role_mapping' => {
+ doc => "security-api-delete-role-mapping",
+ method => "DELETE",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_security", "role_mapping", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_service_token' => {
+ doc => "security-api-delete-service-token",
+ method => "DELETE",
+ parts => { name => {}, namespace => {}, service => {} },
+ paths => [
+ [ { name => 6, namespace => 2, service => 3 }, "_security",
+ "service", "{namespace}",
+ "{service}", "credential",
+ "token", "{name}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.delete_user' => {
+ doc => "security-api-delete-user",
+ method => "DELETE",
+ parts => { username => {} },
+ paths => [ [ { username => 2 }, "_security", "user", "{username}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.disable_user' => {
+ doc => "security-api-disable-user",
+ method => "PUT",
+ parts => { username => {} },
+ paths => [
+ [ { username => 2 }, "_security",
+ "user", "{username}",
+ "_disable"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.disable_user_profile' => {
+ doc => "security-api-disable-user-profile",
+ method => "PUT",
+ parts => { uid => {} },
+ paths =>
+ [ [ { uid => 2 }, "_security", "profile", "{uid}", "_disable" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.enable_user' => {
+ doc => "security-api-enable-user",
+ method => "PUT",
+ parts => { username => {} },
+ paths => [
+ [ { username => 2 }, "_security",
+ "user", "{username}",
+ "_enable"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.enable_user_profile' => {
+ doc => "security-api-enable-user-profile",
+ method => "PUT",
+ parts => { uid => {} },
+ paths =>
+ [ [ { uid => 2 }, "_security", "profile", "{uid}", "_enable" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.enroll_kibana' => {
+ doc => "security-api-kibana-enrollment",
+ parts => {},
+ paths => [ [ {}, "_security", "enroll", "kibana" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.enroll_node' => {
+ doc => "security-api-node-enrollment",
+ parts => {},
+ paths => [ [ {}, "_security", "enroll", "node" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_api_key' => {
+ doc => "security-api-get-api-key",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ id => "string",
+ name => "string",
+ owner => "boolean",
+ realm_name => "string",
+ username => "string",
+ with_limited_by => "boolean",
+ },
+ },
+
+ 'security.get_builtin_privileges' => {
+ doc => "security-api-get-builtin-privileges",
+ parts => {},
+ paths => [ [ {}, "_security", "privilege", "_builtin" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_privileges' => {
+ doc => "security-api-get-privileges",
+ parts => { application => {}, name => {} },
+ paths => [
+ [ { application => 2, name => 3 }, "_security",
+ "privilege", "{application}",
+ "{name}",
+ ],
+ [ { application => 2 }, "_security",
+ "privilege", "{application}"
+ ],
+ [ {}, "_security", "privilege" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_role' => {
+ doc => "security-api-get-role",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_security", "role", "{name}" ],
+ [ {}, "_security", "role" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_role_mapping' => {
+ doc => "security-api-get-role-mapping",
+ parts => { name => { multi => 1 } },
+ paths => [
+ [ { name => 2 }, "_security", "role_mapping", "{name}" ],
+ [ {}, "_security", "role_mapping" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_service_accounts' => {
+ doc => "security-api-get-service-accounts",
+ parts => { namespace => {}, service => {} },
+ paths => [
+ [ { namespace => 2, service => 3 }, "_security",
+ "service", "{namespace}",
+ "{service}",
+ ],
+ [ { namespace => 2 }, "_security", "service", "{namespace}" ],
+ [ {}, "_security", "service" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_service_credentials' => {
+ doc => "security-api-get-service-credentials",
+ parts => { namespace => {}, service => {} },
+ paths => [
+ [ { namespace => 2, service => 3 }, "_security",
+ "service", "{namespace}",
+ "{service}", "credential",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_token' => {
+ body => { required => 1 },
+ doc => "security-api-get-token",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "oauth2", "token" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_user' => {
+ doc => "security-api-get-user",
+ parts => { username => { multi => 1 } },
+ paths => [
+ [ { username => 2 }, "_security", "user", "{username}" ],
+ [ {}, "_security", "user" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ with_profile_uid => "boolean",
+ },
+ },
+
+ 'security.get_user_privileges' => {
+ doc => "security-api-get-user-privileges",
+ parts => {},
+ paths => [ [ {}, "_security", "user", "_privileges" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.get_user_profile' => {
+ doc => "security-api-get-user-profile",
+ parts => { uid => { multi => 1 } },
+ paths => [ [ { uid => 2 }, "_security", "profile", "{uid}" ] ],
+ qs => {
+ data => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'security.grant_api_key' => {
+ body => { required => 1 },
+ doc => "security-api-grant-api-key",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key", "grant" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.has_privileges' => {
+ body => { required => 1 },
+ doc => "security-api-has-privileges",
+ parts => { user => {} },
+ paths => [
+ [ { user => 2 }, "_security",
+ "user", "{user}",
+ "_has_privileges"
+ ],
+ [ {}, "_security", "user", "_has_privileges" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.has_privileges_user_profile' => {
+ body => { required => 1 },
+ doc => "security-api-has-privileges-user-profile",
+ parts => {},
+ paths => [ [ {}, "_security", "profile", "_has_privileges" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.invalidate_api_key' => {
+ body => { required => 1 },
+ doc => "security-api-invalidate-api-key",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_security", "api_key" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.invalidate_token' => {
+ body => { required => 1 },
+ doc => "security-api-invalidate-token",
+ method => "DELETE",
+ parts => {},
+ paths => [ [ {}, "_security", "oauth2", "token" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.oidc_authenticate' => {
+ body => { required => 1 },
+ doc => "security-api-oidc-authenticate",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "oidc", "authenticate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.oidc_logout' => {
+ body => { required => 1 },
+ doc => "security-api-oidc-logout",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "oidc", "logout" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.oidc_prepare_authentication' => {
+ body => { required => 1 },
+ doc => "security-api-oidc-prepare-authentication",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "oidc", "prepare" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.put_privileges' => {
+ body => { required => 1 },
+ doc => "security-api-put-privileges",
+ method => "PUT",
+ parts => {},
+ paths => [ [ {}, "_security", "privilege" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.put_role' => {
+ body => { required => 1 },
+ doc => "security-api-put-role",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_security", "role", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.put_role_mapping' => {
+ body => { required => 1 },
+ doc => "security-api-put-role-mapping",
+ method => "PUT",
+ parts => { name => {} },
+ paths => [ [ { name => 2 }, "_security", "role_mapping", "{name}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.put_user' => {
+ body => { required => 1 },
+ doc => "security-api-put-user",
+ method => "PUT",
+ parts => { username => {} },
+ paths => [ [ { username => 2 }, "_security", "user", "{username}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ refresh => "enum",
+ },
+ },
+
+ 'security.query_api_keys' => {
+ body => {},
+ doc => "security-api-query-api-key",
+ parts => {},
+ paths => [ [ {}, "_security", "_query", "api_key" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ with_limited_by => "boolean",
+ },
+ },
+
+ 'security.saml_authenticate' => {
+ body => { required => 1 },
+ doc => "security-api-saml-authenticate",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "saml", "authenticate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.saml_complete_logout' => {
+ body => { required => 1 },
+ doc => "security-api-saml-complete-logout",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "saml", "complete_logout" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.saml_invalidate' => {
+ body => { required => 1 },
+ doc => "security-api-saml-invalidate",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "saml", "invalidate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.saml_logout' => {
+ body => { required => 1 },
+ doc => "security-api-saml-logout",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "saml", "logout" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.saml_prepare_authentication' => {
+ body => { required => 1 },
+ doc => "security-api-saml-prepare-authentication",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_security", "saml", "prepare" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.saml_service_provider_metadata' => {
+ doc => "security-api-saml-sp-metadata",
+ parts => { realm_name => {} },
+ paths => [
+ [ { realm_name => 3 }, "_security",
+ "saml", "metadata",
+ "{realm_name}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.suggest_user_profiles' => {
+ body => {},
+ doc => "security-api-suggest-user-profile",
+ parts => {},
+ paths => [ [ {}, "_security", "profile", "_suggest" ] ],
+ qs => {
+ data => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'security.update_api_key' => {
+ body => {},
+ doc => "security-api-update-api-key",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_security", "api_key", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'security.update_user_profile_data' => {
+ body => { required => 1 },
+ doc => "security-api-update-user-profile-data",
+ method => "PUT",
+ parts => { uid => {} },
+ paths =>
+ [ [ { uid => 2 }, "_security", "profile", "{uid}", "_data" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ refresh => "enum",
+ },
+ },
+
+ 'shutdown.delete_node' => {
+ doc => "",
+ method => "DELETE",
+ parts => { node_id => {} },
+ paths => [ [ { node_id => 1 }, "_nodes", "{node_id}", "shutdown" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'shutdown.get_node' => {
+ doc => "",
+ parts => { node_id => {} },
+ paths => [
+ [ { node_id => 1 }, "_nodes", "{node_id}", "shutdown" ],
+ [ {}, "_nodes", "shutdown" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'shutdown.put_node' => {
+ body => { required => 1 },
+ doc => "",
+ method => "PUT",
+ parts => { node_id => {} },
+ paths => [ [ { node_id => 1 }, "_nodes", "{node_id}", "shutdown" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.delete_lifecycle' => {
+ doc => "slm-api-delete-policy",
+ method => "DELETE",
+ parts => { policy_id => {} },
+ paths => [ [ { policy_id => 2 }, "_slm", "policy", "{policy_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.execute_lifecycle' => {
+ doc => "slm-api-execute-lifecycle",
+ method => "PUT",
+ parts => { policy_id => {} },
+ paths => [
+ [ { policy_id => 2 }, "_slm",
+ "policy", "{policy_id}",
+ "_execute"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.execute_retention' => {
+ doc => "slm-api-execute-retention",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_slm", "_execute_retention" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.get_lifecycle' => {
+ doc => "slm-api-get-policy",
+ parts => { policy_id => { multi => 1 } },
+ paths => [
+ [ { policy_id => 2 }, "_slm", "policy", "{policy_id}" ],
+ [ {}, "_slm", "policy" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.get_stats' => {
+ doc => "slm-api-get-stats",
+ parts => {},
+ paths => [ [ {}, "_slm", "stats" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.get_status' => {
+ doc => "slm-api-get-status",
+ parts => {},
+ paths => [ [ {}, "_slm", "status" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.put_lifecycle' => {
+ body => {},
+ doc => "slm-api-put-policy",
+ method => "PUT",
+ parts => { policy_id => {} },
+ paths => [ [ { policy_id => 2 }, "_slm", "policy", "{policy_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.start' => {
+ doc => "slm-api-start",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_slm", "start" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'slm.stop' => {
+ doc => "slm-api-stop",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_slm", "stop" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'snapshot.cleanup_repository' => {
+ doc => "clean-up-snapshot-repo-api",
+ method => "POST",
+ parts => { repository => {} },
+ paths => [
+ [ { repository => 1 }, "_snapshot", "{repository}", "_cleanup" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'snapshot.clone' => {
+ body => { required => 1 },
+ doc => "modules-snapshots",
+ method => "PUT",
+ parts => { repository => {}, snapshot => {}, target_snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2, target_snapshot => 4 },
+ "_snapshot",
+ "{repository}",
+ "{snapshot}",
+ "_clone",
+ "{target_snapshot}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'snapshot.create' => {
+ body => {},
+ doc => "modules-snapshots",
+ method => "PUT",
+ parts => { repository => {}, snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'snapshot.create_repository' => {
+ body => { required => 1 },
+ doc => "modules-snapshots",
+ method => "PUT",
+ parts => { repository => {} },
+ paths => [ [ { repository => 1 }, "_snapshot", "{repository}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ verify => "boolean",
+ },
+ },
+
+ 'snapshot.delete' => {
+ doc => "modules-snapshots",
+ method => "DELETE",
+ parts => { repository => {}, snapshot => { multi => 1 } },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'snapshot.delete_repository' => {
+ doc => "modules-snapshots",
+ method => "DELETE",
+ parts => { repository => { multi => 1 } },
+ paths => [ [ { repository => 1 }, "_snapshot", "{repository}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'snapshot.get' => {
+ doc => "modules-snapshots",
+ parts => { repository => {}, snapshot => { multi => 1 } },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ ],
+ ],
+ qs => {
+ after => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ from_sort_value => "string",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ include_repository => "boolean",
+ index_details => "boolean",
+ index_names => "boolean",
+ master_timeout => "time",
+ offset => "integer",
+ order => "enum",
+ size => "integer",
+ slm_policy_filter => "string",
+ sort => "enum",
+ verbose => "boolean",
+ },
+ },
+
+ 'snapshot.get_repository' => {
+ doc => "modules-snapshots",
+ parts => { repository => { multi => 1 } },
+ paths => [
+ [ { repository => 1 }, "_snapshot", "{repository}" ],
+ [ {}, "_snapshot" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ local => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'snapshot.repository_analyze' => {
+ doc => "modules-snapshots",
+ method => "POST",
+ parts => { repository => {} },
+ paths => [
+ [ { repository => 1 }, "_snapshot", "{repository}", "_analyze" ],
+ ],
+ qs => {
+ blob_count => "number",
+ concurrency => "number",
+ detailed => "boolean",
+ early_read_node_count => "number",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ max_blob_size => "string",
+ max_total_data_size => "string",
+ rare_action_probability => "number",
+ rarely_abort_writes => "boolean",
+ read_node_count => "number",
+ seed => "number",
+ timeout => "time",
+ },
+ },
+
+ 'snapshot.restore' => {
+ body => {},
+ doc => "modules-snapshots",
+ method => "POST",
+ parts => { repository => {}, snapshot => {} },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ "_restore",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'snapshot.status' => {
+ doc => "modules-snapshots",
+ parts => { repository => {}, snapshot => { multi => 1 } },
+ paths => [
+ [ { repository => 1, snapshot => 2 }, "_snapshot",
+ "{repository}", "{snapshot}",
+ "_status",
+ ],
+ [ { repository => 1 }, "_snapshot", "{repository}", "_status" ],
+ [ {}, "_snapshot", "_status" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ ignore_unavailable => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+ 'snapshot.verify_repository' => {
+ doc => "modules-snapshots",
+ method => "POST",
+ parts => { repository => {} },
+ paths => [
+ [ { repository => 1 }, "_snapshot", "{repository}", "_verify" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ timeout => "time",
+ },
+ },
+
+ 'sql.clear_cursor' => {
+ body => { required => 1 },
+ doc => "clear-sql-cursor-api",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_sql", "close" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'sql.delete_async' => {
+ doc => "delete-async-sql-search-api",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 3 }, "_sql", "async", "delete", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'sql.get_async' => {
+ doc => "get-async-sql-search-api",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_sql", "async", "{id}" ] ],
+ qs => {
+ delimiter => "string",
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ human => "boolean",
+ keep_alive => "time",
+ wait_for_completion_timeout => "time",
+ },
+ },
+
+ 'sql.get_async_status' => {
+ doc => "get-async-sql-search-status-api",
+ parts => { id => {} },
+ paths => [ [ { id => 3 }, "_sql", "async", "status", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'sql.query' => {
+ body => { required => 1 },
+ doc => "sql-search-api",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_sql" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ format => "string",
+ human => "boolean",
+ },
+ },
+
+ 'sql.translate' => {
+ body => { required => 1 },
+ doc => "sql-translate-api",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_sql", "translate" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'ssl.certificates' => {
+ doc => "security-api-ssl",
+ parts => {},
+ paths => [ [ {}, "_ssl", "certificates" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'tasks.cancel' => {
+ doc => "tasks",
+ method => "POST",
+ parts => { task_id => {} },
+ paths => [
+ [ { task_id => 1 }, "_tasks", "{task_id}", "_cancel" ],
+ [ {}, "_tasks", "_cancel" ],
+ ],
+ qs => {
+ actions => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ nodes => "list",
+ parent_task_id => "string",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'tasks.get' => {
+ doc => "tasks",
+ parts => { task_id => {} },
+ paths => [ [ { task_id => 1 }, "_tasks", "{task_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'tasks.list' => {
+ doc => "tasks",
+ parts => {},
+ paths => [ [ {}, "_tasks" ] ],
+ qs => {
+ actions => "list",
+ detailed => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ group_by => "enum",
+ human => "boolean",
+ nodes => "list",
+ parent_task_id => "string",
+ timeout => "time",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'text_structure.find_structure' => {
+ body => { required => 1 },
+ doc => "find-structure",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_text_structure", "find_structure" ] ],
+ qs => {
+ charset => "string",
+ column_names => "list",
+ delimiter => "string",
+ ecs_compatibility => "string",
+ error_trace => "boolean",
+ explain => "boolean",
+ filter_path => "list",
+ format => "enum",
+ grok_pattern => "string",
+ has_header_row => "boolean",
+ human => "boolean",
+ line_merge_size_limit => "int",
+ lines_to_sample => "int",
+ quote => "string",
+ should_trim_fields => "boolean",
+ timeout => "time",
+ timestamp_field => "string",
+ timestamp_format => "string",
+ },
+ serialize => "bulk",
+ },
+
+ 'transform.delete_transform' => {
+ doc => "delete-transform",
+ method => "DELETE",
+ parts => { transform_id => {} },
+ paths =>
+ [ [ { transform_id => 1 }, "_transform", "{transform_id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'transform.get_transform' => {
+ doc => "get-transform",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform", "{transform_id}" ],
+ [ {}, "_transform" ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ exclude_generated => "boolean",
+ filter_path => "list",
+ from => "int",
+ human => "boolean",
+ size => "int",
+ },
+ },
+
+ 'transform.get_transform_stats' => {
+ doc => "get-transform-stats",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform",
+ "{transform_id}", "_stats"
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ from => "number",
+ human => "boolean",
+ size => "number",
+ },
+ },
+
+ 'transform.preview_transform' => {
+ body => {},
+ doc => "preview-transform",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform",
+ "{transform_id}", "_preview",
+ ],
+ [ {}, "_transform", "_preview" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'transform.put_transform' => {
+ body => { required => 1 },
+ doc => "put-transform",
+ method => "PUT",
+ parts => { transform_id => {} },
+ paths =>
+ [ [ { transform_id => 1 }, "_transform", "{transform_id}" ] ],
+ qs => {
+ defer_validation => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'transform.reset_transform' => {
+ doc => "reset-transform",
+ method => "POST",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform",
+ "{transform_id}", "_reset"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'transform.start_transform' => {
+ doc => "start-transform",
+ method => "POST",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform",
+ "{transform_id}", "_start"
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'transform.stop_transform' => {
+ doc => "stop-transform",
+ method => "POST",
+ parts => { transform_id => {} },
+ paths => [
+ [ { transform_id => 1 }, "_transform", "{transform_id}",
+ "_stop"
+ ],
+ ],
+ qs => {
+ allow_no_match => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ force => "boolean",
+ human => "boolean",
+ timeout => "time",
+ wait_for_checkpoint => "boolean",
+ wait_for_completion => "boolean",
+ },
+ },
+
+ 'transform.update_transform' => {
+ body => { required => 1 },
+ doc => "update-transform",
+ method => "POST",
+ parts => { transform_id => { required => 1 } },
+ paths => [
+ [ { transform_id => 1 }, "_transform",
+ "{transform_id}", "_update",
+ ],
+ ],
+ qs => {
+ defer_validation => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'transform.upgrade_transforms' => {
+ doc => "upgrade-transforms",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_transform", "_upgrade" ] ],
+ qs => {
+ dry_run => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ timeout => "time",
+ },
+ },
+
+ 'watcher.ack_watch' => {
+ doc => "watcher-api-ack-watch",
+ method => "PUT",
+ parts => { action_id => { multi => 1 }, watch_id => {} },
+ paths => [
+ [ { action_id => 4, watch_id => 2 }, "_watcher",
+ "watch", "{watch_id}",
+ "_ack", "{action_id}",
+ ],
+ [ { watch_id => 2 }, "_watcher", "watch", "{watch_id}", "_ack" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.activate_watch' => {
+ doc => "watcher-api-activate-watch",
+ method => "PUT",
+ parts => { watch_id => {} },
+ paths => [
+ [ { watch_id => 2 }, "_watcher",
+ "watch", "{watch_id}",
+ "_activate",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.deactivate_watch' => {
+ doc => "watcher-api-deactivate-watch",
+ method => "PUT",
+ parts => { watch_id => {} },
+ paths => [
+ [ { watch_id => 2 }, "_watcher",
+ "watch", "{watch_id}",
+ "_deactivate",
+ ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.delete_watch' => {
+ doc => "watcher-api-delete-watch",
+ method => "DELETE",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_watcher", "watch", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.execute_watch' => {
+ body => {},
+ doc => "watcher-api-execute-watch",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [
+ [ { id => 2 }, "_watcher", "watch", "{id}", "_execute" ],
+ [ {}, "_watcher", "watch", "_execute" ],
+ ],
+ qs => {
+ debug => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'watcher.get_watch' => {
+ doc => "watcher-api-get-watch",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_watcher", "watch", "{id}" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.put_watch' => {
+ body => {},
+ doc => "watcher-api-put-watch",
+ method => "PUT",
+ parts => { id => {} },
+ paths => [ [ { id => 2 }, "_watcher", "watch", "{id}" ] ],
+ qs => {
+ active => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ version => "number",
+ },
+ },
+
+ 'watcher.query_watches' => {
+ body => {},
+ doc => "watcher-api-query-watches",
+ parts => {},
+ paths => [ [ {}, "_watcher", "_query", "watches" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.start' => {
+ doc => "watcher-api-start",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_watcher", "_start" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'watcher.stats' => {
+ doc => "watcher-api-stats",
+ parts => { metric => { multi => 1 } },
+ paths => [
+ [ { metric => 2 }, "_watcher", "stats", "{metric}" ],
+ [ {}, "_watcher", "stats" ],
+ ],
+ qs => {
+ emit_stacktraces => "boolean",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'watcher.stop' => {
+ doc => "watcher-api-stop",
+ method => "POST",
+ parts => {},
+ paths => [ [ {}, "_watcher", "_stop" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean"
+ },
+ },
+
+ 'xpack.info' => {
+ doc => "info-api",
+ parts => {},
+ paths => [ [ {}, "_xpack" ] ],
+ qs => {
+ accept_enterprise => "boolean",
+ categories => "list",
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ },
+ },
+
+ 'xpack.usage' => {
+ doc => "usage-api",
+ parts => {},
+ paths => [ [ {}, "_xpack", "usage" ] ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ master_timeout => "time",
+ },
+ },
+
+#=== AUTOGEN - END ===
+
+);
+
+__PACKAGE__->_qs_init( \%API );
+1;
+
+__END__
+
+# ABSTRACT: This class contains the spec for the Elasticsearch APIs
+
+=head1 DESCRIPTION
+
+All of the Elasticsearch APIs are defined in this role. The example given below
+is the definition for the L<Search::Elasticsearch::Client::8_0::Direct/index()> method:
+
+ 'index' => {
+ body => { required => 1 },
+ doc => "docs-index_",
+ method => "POST",
+ parts => { id => {}, index => {}, type => {} },
+ paths => [
+ [ { id => 2, index => 0, type => 1 }, "{index}",
+ "{type}", "{id}"
+ ],
+ [ { id => 2, index => 0 }, "{index}", "_doc", "{id}" ],
+ [ { index => 0, type => 1 }, "{index}", "{type}" ],
+ [ { index => 0 }, "{index}", "_doc" ],
+ ],
+ qs => {
+ error_trace => "boolean",
+ filter_path => "list",
+ human => "boolean",
+ if_primary_term => "number",
+ if_seq_no => "number",
+ op_type => "enum",
+ pipeline => "string",
+ refresh => "enum",
+ require_alias => "boolean",
+ routing => "string",
+ timeout => "time",
+ version => "number",
+ version_type => "enum",
+ wait_for_active_shards => "string",
+ },
+ }
+
+These definitions can be used by different L<Search::Elasticsearch::Role::Client>
+implementations to provide distinct user interfaces.
+
+=head1 METHODS
+
+=head2 C<api()>
+
+ $defn = $api->api($name);
+
+The only method in this class is the C<api()> method which takes the name
+of the I<action> and returns its definition. Actions in the
+C<indices> or C<cluster> namespace use the namespace as a prefix, eg:
+
+ $defn = $e->api('indices.create');
+ $defn = $e->api('cluster.node_stats');
+
+=head1 SEE ALSO
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Role::API>
+
+=item *
+
+L<Search::Elasticsearch::Client::8_0::Direct>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/8_0/Role/Bulk.pm b/lib/Search/Elasticsearch/Client/8_0/Role/Bulk.pm
new file mode 100644
index 0000000..9b048b1
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Role/Bulk.pm
@@ -0,0 +1,280 @@
+# 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 Search::Elasticsearch::Client::8_0::Role::Bulk;
+
+use Moo::Role;
+requires 'add_action', 'flush';
+
+use Search::Elasticsearch::Util qw(parse_params throw);
+use namespace::clean;
+
+has 'es' => ( is => 'ro', required => 1 );
+has 'max_count' => ( is => 'rw', default => 1_000 );
+has 'max_size' => ( is => 'rw', default => 1_000_000 );
+has 'max_time' => ( is => 'rw', default => 0 );
+
+has 'on_success' => ( is => 'ro', default => 0 );
+has 'on_error' => ( is => 'lazy' );
+has 'on_conflict' => ( is => 'ro', default => 0 );
+has 'verbose' => ( is => 'rw' );
+
+has '_buffer' => ( is => 'ro', default => sub { [] } );
+has '_buffer_size' => ( is => 'rw', default => 0 );
+has '_buffer_count' => ( is => 'rw', default => 0 );
+has '_serializer' => ( is => 'lazy' );
+has '_bulk_args' => ( is => 'ro' );
+has '_last_flush' => ( is => 'rw', default => sub {time} );
+has '_metadata_params' => ( is => 'ro' );
+has '_update_params' => ( is => 'ro' );
+has '_required_params' => ( is => 'ro' );
+
+our %Actions = (
+ 'index' => 1,
+ 'create' => 1,
+ 'update' => 1,
+ 'delete' => 1
+);
+
+#===================================
+sub _build__serializer { shift->es->transport->serializer }
+#===================================
+
+#===================================
+sub _build_on_error {
+#===================================
+ my $self = shift;
+ my $serializer = $self->_serializer;
+ return sub {
+ my ( $action, $result, $src ) = @_;
+ warn( "Bulk error [$action]: " . $serializer->encode($result) );
+ };
+}
+
+#===================================
+sub BUILDARGS {
+#===================================
+ my ( $class, $params ) = parse_params(@_);
+ my $es = $params->{es} or throw( 'Param', 'Missing required param <es>' );
+ $params->{_metadata_params} = $es->api('bulk.metadata')->{params};
+ $params->{_update_params} = $es->api('bulk.update')->{params};
+ $params->{_required_params} = $es->api('bulk.required')->{params};
+ my $bulk_spec = $es->api('bulk');
+ my %args;
+ for ( keys %{ $bulk_spec->{qs} }, keys %{ $bulk_spec->{parts} } ) {
+ $args{$_} = delete $params->{$_}
+ if exists $params->{$_};
+ }
+ $params->{_bulk_args} = \%args;
+ return $params;
+}
+
+#===================================
+sub index {
+#===================================
+ shift->add_action( map { ( 'index' => $_ ) } @_ );
+}
+
+#===================================
+sub create {
+#===================================
+ shift->add_action( map { ( 'create' => $_ ) } @_ );
+}
+
+#===================================
+sub delete {
+#===================================
+ shift->add_action( map { ( 'delete' => $_ ) } @_ );
+}
+
+#===================================
+sub update {
+#===================================
+ shift->add_action( map { ( 'update' => $_ ) } @_ );
+}
+
+#===================================
+sub create_docs {
+#===================================
+ my $self = shift;
+ $self->add_action( map { ( 'create' => { source => $_ } ) } @_ );
+}
+
+#===================================
+sub delete_ids {
+#===================================
+ my $self = shift;
+ $self->add_action( map { ( 'delete' => { _id => $_ } ) } @_ );
+}
+
+#===================================
+sub _encode_action {
+#===================================
+ my $self = shift;
+ my $action = shift || '';
+ my $orig = shift;
+
+ throw( 'Param', "Unrecognised action <$action>" )
+ unless $Actions{$action};
+
+ throw( 'Param', "Missing <params> for action <$action>" )
+ unless ref($orig) eq 'HASH';
+
+ my %metadata;
+ my $params = {%$orig};
+ my $serializer = $self->_serializer;
+
+ my $meta_params = $self->_metadata_params;
+ for ( keys %$meta_params ) {
+ next unless exists $params->{$_};
+ $metadata{ $meta_params->{$_} } = delete $params->{$_};
+ }
+
+ for ( @{ $self->_required_params } ) {
+ throw( 'Param', "Missing required param <$_>" )
+ unless $metadata{"_$_"} || $self->_bulk_args->{$_};
+ }
+
+ my $source;
+ if ( $action eq 'update' ) {
+ for ( @{ $self->_update_params } ) {
+ $source->{$_} = delete $params->{$_}
+ if exists $params->{$_};
+ }
+ }
+ elsif ( $action ne 'delete' ) {
+ $source = delete $params->{source}
+ || throw( 'Param',
+ "Missing <source> for action <$action>: "
+ . $serializer->encode($orig) );
+ }
+
+ throw( "Unknown params <"
+ . ( join ',', sort keys %$params )
+ . "> in <$action>: "
+ . $serializer->encode($orig) )
+ if keys %$params;
+
+ return map { $serializer->encode($_) }
+ grep {$_} ( { $action => \%metadata }, $source );
+}
+
+#===================================
+sub _report {
+#===================================
+ my ( $self, $buffer, $results ) = @_;
+ my $on_success = $self->on_success;
+ my $on_error = $self->on_error;
+ my $on_conflict = $self->on_conflict;
+
+ # assume errors if key not present, bwc
+ $results->{errors} = 1 unless exists $results->{errors};
+
+ return
+ unless $on_success
+ || ( $results->{errors} and $on_error || $on_conflict );
+
+ my $serializer = $self->_serializer;
+
+ my $j = 0;
+
+ for my $item ( @{ $results->{items} } ) {
+ my ( $action, $result ) = %$item;
+ my @args = ($action);
+ if ( my $error = $result->{error} ) {
+ if ($on_conflict) {
+ my ( $is_conflict, $version )
+ = $self->_is_conflict_error($error);
+ if ($is_conflict) {
+ $on_conflict->( $action, $result, $j, $version );
+ next;
+ }
+ }
+ $on_error && $on_error->( $action, $result, $j );
+ }
+ else {
+ $on_success && $on_success->( $action, $result, $j );
+ }
+ $j++;
+ }
+}
+
+#===================================
+sub _is_conflict_error {
+#===================================
+ my ( $self, $error ) = @_;
+ my $version;
+ if ( ref($error) eq 'HASH' ) {
+ return 1 if $error->{type} eq 'document_already_exists_exception';
+ return unless $error->{type} eq 'version_conflict_engine_exception';
+ $error->{reason} =~ /version.conflict,.current.(?:version.)?\[(\d+)\]/;
+ return ( 1, $1 );
+ }
+ return unless $error =~ /
+ DocumentAlreadyExistsException
+ |version.conflict,.current.\[(\d+)\]
+ /x;
+ return ( 1, $1 );
+}
+
+#===================================
+sub clear_buffer {
+#===================================
+ my $self = shift;
+ @{ $self->_buffer } = ();
+ $self->_buffer_size(0);
+ $self->_buffer_count(0);
+}
+
+#===================================
+sub _doc_transformer {
+#===================================
+ my ( $self, $params ) = @_;
+
+ my $bulk_args = $self->_bulk_args;
+ my %allowed = map { $_ => 1, "_$_" => 1 }
+ ( @{ $self->_metadata_params }, 'source' );
+ $allowed{fields} = 1;
+
+ delete @allowed{ 'index', '_index' } if $bulk_args->{index};
+ delete @allowed{ 'type', '_type' } if $bulk_args->{type};
+
+ my $version_type = $params->{version_type};
+ my $transform = $params->{transform};
+
+ return sub {
+ my %doc = %{ shift() };
+ for ( keys %doc ) {
+ delete $doc{$_} unless $allowed{$_};
+ }
+
+ if ( my $fields = delete $doc{fields} ) {
+ for (qw(_routing routing _parent parent)) {
+ $doc{$_} = $fields->{$_}
+ if exists $fields->{$_};
+ }
+ }
+ $doc{_version_type} = $version_type if $version_type;
+
+ return \%doc unless $transform;
+ return $transform->( \%doc );
+ };
+}
+
+1;
+
+# ABSTRACT: Provides common functionality to L<Elasticseach::Client::8_0::Bulk> and L<Search::Elasticsearch::Client::8_0::Async::Bulk>
diff --git a/lib/Search/Elasticsearch/Client/8_0/Role/Scroll.pm b/lib/Search/Elasticsearch/Client/8_0/Role/Scroll.pm
new file mode 100644
index 0000000..c0cabd2
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Role/Scroll.pm
@@ -0,0 +1,63 @@
+# 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 Search::Elasticsearch::Client::8_0::Role::Scroll;
+
+use Moo::Role;
+requires 'finish';
+use Search::Elasticsearch::Util qw(parse_params throw);
+use Devel::GlobalDestruction;
+use namespace::clean;
+has 'es' => ( is => 'ro', required => 1 );
+has 'scroll' => ( is => 'ro' );
+has 'total' => ( is => 'rwp' );
+has 'max_score' => ( is => 'rwp' );
+has 'facets' => ( is => 'rwp' );
+has 'aggregations' => ( is => 'rwp' );
+has 'suggest' => ( is => 'rwp' );
+has 'took' => ( is => 'rwp' );
+has 'total_took' => ( is => 'rwp' );
+has 'search_params' => ( is => 'ro' );
+has 'is_finished' => ( is => 'rwp', default => '' );
+has '_pid' => ( is => 'ro', default => sub {$$} );
+has '_scroll_id' => ( is => 'rwp', clearer => 1, predicate => 1 );
+
+#===================================
+sub scroll_request {
+#===================================
+ my $self = shift;
+ throw( 'Illegal',
+ 'Scroll requests are not fork safe and may only be '
+ . 'refilled by the same process that created the instance.' )
+ if $self->_pid != $$;
+
+ my %args = ( scroll => $self->scroll );
+ $args{body} = { scroll_id => $self->_scroll_id };
+ $self->es->scroll(%args);
+}
+
+#===================================
+sub DEMOLISH {
+#===================================
+ my $self = shift or return;
+ return if in_global_destruction;
+ $self->finish;
+}
+
+1;
+
+# ABSTRACT: Provides common functionality to L<Search::Elasticsearch::Client::8_0::Scroll> and L<Search::Elasticsearch::Client::8_0::Async::Scroll>
diff --git a/lib/Search/Elasticsearch/Client/8_0/Scroll.pm b/lib/Search/Elasticsearch/Client/8_0/Scroll.pm
new file mode 100644
index 0000000..525e52d
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/Scroll.pm
@@ -0,0 +1,368 @@
+# 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 Search::Elasticsearch::Client::8_0::Scroll;
+
+use Moo;
+use Search::Elasticsearch::Util qw(parse_params throw);
+use namespace::clean;
+
+has '_buffer' => ( is => 'ro' );
+
+with 'Search::Elasticsearch::Role::Is_Sync',
+ 'Search::Elasticsearch::Client::8_0::Role::Scroll';
+
+#===================================
+sub BUILDARGS {
+#===================================
+ my ( $class, $params ) = parse_params(@_);
+ my $es = delete $params->{es};
+ my $scroll = $params->{scroll} ||= '1m';
+
+ my $results = $es->search($params);
+
+ my $total = $results->{hits}{total};
+ if (ref $total) {
+ $total = $total->{value}
+ }
+
+ return {
+ es => $es,
+ scroll => $scroll,
+ aggregations => $results->{aggregations},
+ facets => $results->{facets},
+ suggest => $results->{suggest},
+ took => $results->{took},
+ total_took => $results->{took},
+ total => $total,
+ max_score => $results->{hits}{max_score},
+ _buffer => $results->{hits}{hits},
+ $total
+ ? ( _scroll_id => $results->{_scroll_id} )
+ : ( is_finished => 1 )
+ };
+}
+
+#===================================
+sub next {
+#===================================
+ my ( $self, $n ) = @_;
+ $n ||= 1;
+ while ( $self->_has_scroll_id and $self->buffer_size < $n ) {
+ $self->refill_buffer;
+ }
+ my @return = splice( @{ $self->_buffer }, 0, $n );
+ $self->finish if @return < $n;
+ return wantarray ? @return : $return[-1];
+}
+
+#===================================
+sub drain_buffer {
+#===================================
+ my $self = shift;
+ return splice( @{ $self->_buffer } );
+}
+
+#===================================
+sub buffer_size { 0 + @{ shift->_buffer } }
+#===================================
+
+#===================================
+sub refill_buffer {
+#===================================
+ my $self = shift;
+
+ return 0 if $self->is_finished;
+
+ my $buffer = $self->_buffer;
+ my $scroll_id = $self->_scroll_id
+ || return 0 + @$buffer;
+
+ my $results = $self->scroll_request;
+
+ my $hits = $results->{hits}{hits};
+ $self->_set_total_took( $self->total_took + $results->{took} );
+
+ if ( @$hits == 0 ) {
+ $self->_clear_scroll_id;
+ }
+ else {
+ $self->_set__scroll_id( $results->{_scroll_id} );
+ push @$buffer, @$hits;
+ }
+ $self->finish if @$buffer == 0;
+ return 0 + @$buffer;
+}
+
+#===================================
+sub finish {
+#===================================
+ my $self = shift;
+ return if $self->is_finished || $self->_pid != $$;
+
+ $self->_set_is_finished(1);
+ @{ $self->_buffer } = ();
+
+ my $scroll_id = $self->_scroll_id or return;
+ $self->_clear_scroll_id;
+
+ my %args = ( body => { scroll_id => $scroll_id } );
+ eval { $self->es->clear_scroll(%args) };
+ return 1;
+}
+
+1;
+
+__END__
+
+# ABSTRACT: A helper module for scrolled searches
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch;
+
+ my $es = Search::Elasticsearch->new;
+
+ my $scroll = $es->scroll_helper(
+ index => 'my_index',
+ body => {
+ query => {...},
+ size => 1000,
+ sort => '_doc'
+ }
+ );
+
+ say "Total hits: ". $scroll->total;
+
+ while (my $doc = $scroll->next) {
+ # do something
+ }
+
+=head1 DESCRIPTION
+
+A I<scrolled search> is a search that allows you to keep pulling results
+until there are no more matching results, much like a cursor in an SQL
+database.
+
+Unlike paginating through results (with the C<from> parameter in
+L<search()|Search::Elasticsearch::Client::8_0::Direct/search()>),
+scrolled searches take a snapshot of the current state of the index. Even
+if you keep adding new documents to the index or updating existing documents,
+a scrolled search will only see the index as it was when the search began.
+
+This module is a helper utility that wraps the functionality of the
+L<search()|Search::Elasticsearch::Client::8_0::Direct/search()> and
+L<scroll()|Search::Elasticsearch::Client::8_0::Direct/scroll()> methods to make
+them easier to use.
+
+This class does L<Search::Elasticsearch::Client::8_0::Role::Scroll> and
+L<Search::Elasticsearch::Role::Is_Sync>.
+
+
+=head1 USE CASES
+
+There are two primary use cases:
+
+=head2 Pulling enough results
+
+Perhaps you want to group your results by some field, and you don't know
+exactly how many results you will need in order to return 10 grouped
+results. With a scrolled search you can keep pulling more results
+until you have enough. For instance, you can search emails in a mailing
+list, and return results grouped by C<thread_id>:
+
+ my (%groups,@results);
+
+ my $scroll = $es->scroll_helper(
+ index => 'my_emails',
+ type => 'email',
+ body => { query => {... some query ... }}
+ );
+
+ my $doc;
+ while (@results < 10 and $doc = $scroll->next) {
+
+ my $thread = $doc->{_source}{thread_id};
+
+ unless ($groups{$thread}) {
+ $groups{$thread} = [];
+ push @results, $groups{$thread};
+ }
+ push @{$groups{$thread}},$doc;
+
+ }
+
+
+=head2 Extracting all documents
+
+Often you will want to extract all (or a subset of) documents in an index.
+If you want to change your type mappings, you will need to reindex all of your
+data. Or perhaps you want to move a subset of the data in one index into
+a new dedicated index. In these cases, you don't care about sort
+order, you just want to retrieve all documents which match a query, and do
+something with them. For instance, to retrieve all the docs for a particular
+C<client_id>:
+
+ my $scroll = $es->scroll_helper(
+ index => 'my_index',
+ size => 1000,
+ body => {
+ query => {
+ match => {
+ client_id => 123
+ }
+ },
+ sort => '_doc'
+ }
+ );
+
+ while (my $doc = $scroll->next) {
+ # do something
+ }
+
+Very often the I<something> that you will want to do with these results
+involves bulk-indexing them into a new index. The easiest way to
+do this is to use the built-in L<Search::Elasticsearch::Client::8_0::Direct/reindex()>
+functionality provided by Elasticsearch.
+
+=head1 METHODS
+
+=head2 C<new()>
+
+ use Search::Elasticsearch;
+
+ my $es = Search::Elasticsearch->new(...);
+ my $scroll = $es->scroll_helper(
+ scroll => '1m', # optional
+ %search_params
+ );
+
+The L<Search::Elasticsearch::Client::8_0::Direct/scroll_helper()> method loads
+L<Search::Elasticsearch::Client::8_0::Scroll> class and calls L</new()>,
+passing in any arguments.
+
+You can specify a C<scroll> duration (which defaults to C<"1m">).
+Any other parameters are passed directly to L<Search::Elasticsearch::Client::8_0::Direct/search()>.
+
+The C<scroll> duration tells Elasticearch how long it should keep the scroll
+alive. B<Note>: this duration doesn't need to be long enough to process
+all results, just long enough to process a single B<batch> of results.
+The expiry gets renewed for another C<scroll> period every time new
+a new batch of results is retrieved from the cluster.
+
+By default, the C<scroll_id> is passed as the C<body> to the
+L<scroll|Search::Elasticsearch::Client::8_0::Direct/scroll()> request.
+
+The C<scroll> request uses C<GET> by default. To use C<POST> instead,
+set L<send_get_body_as|Search::Elasticsearch::Transport/send_get_body_as> to
+C<POST>.
+
+=head2 C<next()>
+
+ $doc = $scroll->next;
+ @docs = $scroll->next($num);
+
+The C<next()> method returns the next result, or the next C<$num> results
+(pulling more results if required). If all results have been exhausted,
+it returns an empty list.
+
+=head2 C<drain_buffer()>
+
+ @docs = $scroll->drain_buffer;
+
+The C<drain_buffer()> method returns all of the documents currently in the
+buffer, without fetching any more from the cluster.
+
+=head2 C<refill_buffer()>
+
+ $total = $scroll->refill_buffer;
+
+The C<refill_buffer()> method fetches the next batch of results from the
+cluster, stores them in the buffer, and returns the total number of docs
+currently in the buffer.
+
+=head2 C<buffer_size()>
+
+ $total = $scroll->buffer_size;
+
+The C<buffer_size()> method returns the total number of docs currently in
+the buffer.
+
+=head2 C<finish()>
+
+ $scroll->finish;
+
+The C<finish()> method clears out the buffer, sets L</is_finished()> to C<true>
+and tries to clear the C<scroll_id> on Elasticsearch. This API is only
+supported since v0.90.6, but the call to C<clear_scroll> is wrapped in an
+C<eval> so the C<finish()> method can be safely called with any version
+of Elasticsearch.
+
+When the C<$scroll> instance goes out of scope, L</finish()> is called
+automatically if required.
+
+=head2 C<is_finished()>
+
+ $bool = $scroll->is_finished;
+
+A flag which returns C<true> if all results have been processed or
+L</finish()> has been called.
+
+=head1 INFO ACCESSORS
+
+The information from the original search is returned via the following
+accessors:
+
+=head2 C<total>
+
+The total number of documents that matched your query.
+
+=head2 C<max_score>
+
+The maximum score of any documents in your query.
+
+=head2 C<aggregations>
+
+Any aggregations that were specified, or C<undef>
+
+=head2 C<facets>
+
+Any facets that were specified, or C<undef>
+
+=head2 C<suggest>
+
+Any suggestions that were specified, or C<undef>
+
+=head2 C<took>
+
+How long the original search took, in milliseconds
+
+=head2 C<took_total>
+
+How long the original search plus all subsequent batches took, in milliseconds.
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Client::8_0::Direct/search()>
+
+=item * L<Search::Elasticsearch::Client::8_0::Direct/scroll()>
+
+=item * L<Search::Elasticsearch::Client::8_0::Direct/reindex()>
+
+=back
diff --git a/lib/Search/Elasticsearch/Client/8_0/TestServer.pm b/lib/Search/Elasticsearch/Client/8_0/TestServer.pm
new file mode 100644
index 0000000..87c68a4
--- /dev/null
+++ b/lib/Search/Elasticsearch/Client/8_0/TestServer.pm
@@ -0,0 +1,47 @@
+# 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 Search::Elasticsearch::Client::8_0::TestServer;
+
+use strict;
+use warnings;
+
+#===================================
+sub command_line {
+#===================================
+ my ( $class, $ts, $pid_file, $dir, $transport, $http ) = @_;
+
+ return (
+ $ts->es_home . '/bin/elasticsearch',
+ '-p',
+ $pid_file->filename,
+ map {"-E$_"} (
+ 'path.data=' . $dir,
+ 'network.host=127.0.0.1',
+ 'cluster.name=es_test',
+ 'discovery.zen.ping_timeout=1s',
+ 'discovery.zen.ping.unicast.hosts=127.0.0.1:' . $ts->es_port,
+ 'transport.tcp.port=' . $transport,
+ 'http.port=' . $http,
+ @{ $ts->conf }
+ )
+ );
+}
+
+1
+
+# ABSTRACT: Client-specific backend for Search::Elasticsearch::TestServer
diff --git a/lib/Search/Elasticsearch/Cxn/AEHTTP.pm b/lib/Search/Elasticsearch/Cxn/AEHTTP.pm
new file mode 100644
index 0000000..16ae98e
--- /dev/null
+++ b/lib/Search/Elasticsearch/Cxn/AEHTTP.pm
@@ -0,0 +1,280 @@
+# 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 Search::Elasticsearch::Cxn::AEHTTP;
+
+use AnyEvent::HTTP qw(http_request);
+use Promises qw(deferred);
+use Try::Tiny;
+use Moo;
+
+with 'Search::Elasticsearch::Role::Cxn::Async';
+with 'Search::Elasticsearch::Role::Cxn',
+ 'Search::Elasticsearch::Role::Is_Async';
+
+has '_tls_ctx' => ( is => 'lazy' );
+
+use namespace::clean;
+
+#===================================
+sub _build__tls_ctx {
+#===================================
+ my $self = shift;
+ return 'low' unless $self->has_ssl_options;
+ require AnyEvent::TLS;
+ return AnyEvent::TLS->new( %{ $self->ssl_options } );
+}
+
+#===================================
+sub perform_request {
+#===================================
+ my ( $self, $params ) = @_;
+ my $uri = $self->build_uri($params);
+ my $method = $params->{method};
+ my %headers = ( %{ $self->default_headers } );
+ my $data = $params->{data};
+ if ( defined $data ) {
+ $headers{'Content-Type'} = $params->{mime_type};
+ $headers{'Content-Encoding'} = $params->{encoding}
+ if $params->{encoding};
+ }
+
+ my $deferred = deferred;
+
+ http_request(
+ $method => $uri,
+ headers => \%headers,
+ timeout => $params->{timeout} || $self->request_timeout,
+ body => $data,
+ persistent => 0,
+ session => $self->_pid,
+ ( %{ $self->handle_args } ),
+ ( $self->is_https ? ( tls_ctx => $self->_tls_ctx ) : () ),
+ sub {
+ my ( $body, $headers ) = @_;
+ try {
+ my ( $code, $response ) = $self->process_response(
+ $params, # request
+ delete $headers->{Status}, # code
+ delete $headers->{Reason}, # msg
+ $body, # body
+ $headers # headers
+ );
+ $deferred->resolve( $code, $response );
+ }
+ catch {
+ $deferred->reject($_);
+ }
+
+ }
+ );
+ $deferred->promise;
+}
+
+#===================================
+sub error_from_text {
+#===================================
+ local $_ = $_[2];
+ return
+ /[Tt]imed out/ ? 'Timeout'
+ : /certificate verify failed/ ? 'SSL'
+ : /Invalid argument/ ? 'Cxn'
+ : 'Request';
+}
+
+1;
+
+# ABSTRACT: An async Cxn implementation which uses AnyEvent::HTTP
+
+=head1 DESCRIPTION
+
+Provides the default async HTTP Cxn class and is based on L<AnyEvent::HTTP>.
+The AEHTTP backend is fast, uses pure Perl, support proxies and https
+and provides persistent connections.
+
+This class does L<Search::Elasticsearch::Role::Cxn>, whose documentation
+provides more information, L<Search::Elasticsearch::Role::Async::Cxn>,
+and L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 CONFIGURATION
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<node|Search::Elasticsearch::Role::Cxn/"node">
+
+=item * L<max_content_length|Search::Elasticsearch::Role::Cxn/"max_content_length">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"gzip">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"deflate">
+
+=item * L<request_timeout|Search::Elasticsearch::Role::Cxn/"request_timeout">
+
+=item * L<ping_timeout|Search::Elasticsearch::Role::Cxn/"ping_timeout">
+
+=item * L<dead_timeout|Search::Elasticsearch::Role::Cxn/"dead_timeout">
+
+=item * L<max_dead_timeout|Search::Elasticsearch::Role::Cxn/"max_dead_timeout">
+
+=item * L<sniff_request_timeout|Search::Elasticsearch::Role::Cxn/"sniff_request_timeout">
+
+=item * L<sniff_timeout|Search::Elasticsearch::Role::Cxn/"sniff_timeout">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"handle_args">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"default_qs_params">
+
+=back
+
+=head1 SSL/TLS
+
+L<Search::Elasticsearch::Cxn::AEHTTP> uses L<AnyEvent::TLS> to support
+HTTPS. By default, no validation of the remote host is performed.
+
+This behaviour can be changed by passing the C<ssl_options> parameter
+with any options accepted by L<AnyEvent::TLS>. For instance, to check
+that the remote host has a trusted certificate, and to avoid man-in-the-middle
+attacks, you could do the following:
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new(
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ verify => 1,
+ verify_peername => 'https'
+ ca_file => '/path/to/cacert.pem'
+ }
+ );
+
+If the remote server cannot be verified, an
+L<Search::Elasticsearch::Error|SSL error> will be thrown.
+
+If you want your client to present its own certificate to the remote
+server, then use:
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new(
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ verify => 1,
+ verify_peername => 'https'
+ ca_file => '/path/to/cacert.pem'
+ cert_file => '/path/to/client.pem',
+ }
+ );
+
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+ $self->perform_request({
+ # required
+ method => 'GET|HEAD|POST|PUT|DELETE',
+ path => '/path/of/request',
+ qs => \%query_string_params,
+
+ # optional
+ data => $body_as_string,
+ mime_type => 'application/json',
+ timeout => $timeout
+ })
+ ->then(sub { my ($status,body) = @_; ...})
+
+Sends the request to the associated Elasticsearch node and returns
+a C<$status> code and the decoded response C<$body>, or throws an
+error if the request failed.
+
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<scheme()|Search::Elasticsearch::Role::Cxn/"scheme()">
+
+=item * L<is_https()|Search::Elasticsearch::Role::Cxn/"is_https()">
+
+=item * L<userinfo()|Search::Elasticsearch::Role::Cxn/"userinfo()">
+
+=item * L<default_headers()|Search::Elasticsearch::Role::Cxn/"default_headers()">
+
+=item * L<max_content_length()|Search::Elasticsearch::Role::Cxn/"max_content_length()">
+
+=item * L<build_uri()|Search::Elasticsearch::Role::Cxn/"build_uri()">
+
+=item * L<host()|Search::Elasticsearch::Role::Cxn/"host()">
+
+=item * L<port()|Search::Elasticsearch::Role::Cxn/"port()">
+
+=item * L<uri()|Search::Elasticsearch::Role::Cxn/"uri()">
+
+=item * L<is_dead()|Search::Elasticsearch::Role::Cxn/"is_dead()">
+
+=item * L<is_live()|Search::Elasticsearch::Role::Cxn/"is_live()">
+
+=item * L<next_ping()|Search::Elasticsearch::Role::Cxn/"next_ping()">
+
+=item * L<ping_failures()|Search::Elasticsearch::Role::Cxn/"ping_failures()">
+
+=item * L<mark_dead()|Search::Elasticsearch::Role::Cxn/"mark_dead()">
+
+=item * L<mark_live()|Search::Elasticsearch::Role::Cxn/"mark_live()">
+
+=item * L<force_ping()|Search::Elasticsearch::Role::Cxn/"force_ping()">
+
+=item * L<pings_ok()|Search::Elasticsearch::Role::Cxn/"pings_ok()">
+
+=item * L<sniff()|Search::Elasticsearch::Role::Cxn/"sniff()">
+
+=item * L<process_response()|Search::Elasticsearch::Role::Cxn/"process_response()">
+
+=back
+
+From L<Search::Elasticsearch::Role::Async::Cxn>
+
+=over
+
+=item * L<pings_ok()|Search::Elasticsearch::Role::Async::Cxn/"pings_ok()">
+
+=item * L<sniff()|Search::Elasticsearch::Role::Async::Cxn/"sniff()">
+
+=back
+
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Role::Cxn::Mojo>
+
+=back
+
+
diff --git a/lib/Search/Elasticsearch/Cxn/Factory.pm b/lib/Search/Elasticsearch/Cxn/Factory.pm
new file mode 100644
index 0000000..39c6772
--- /dev/null
+++ b/lib/Search/Elasticsearch/Cxn/Factory.pm
@@ -0,0 +1,68 @@
+# 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 Search::Elasticsearch::Cxn::Factory;
+
+use Moo;
+use Search::Elasticsearch::Util qw(parse_params load_plugin);
+use namespace::clean;
+
+has 'cxn_class' => ( is => 'ro', required => 1 );
+has '_factory' => ( is => 'ro', required => 1 );
+has 'default_host' => ( is => 'ro', default => 'http://localhost:9200' );
+has 'max_content_length' => ( is => 'rw', default => 104_857_600 );
+
+#===================================
+sub BUILDARGS {
+#===================================
+ my ( $class, $params ) = parse_params(@_);
+ my %args = (%$params);
+ delete $args{nodes};
+
+ my $cxn_class
+ = load_plugin( 'Search::Elasticsearch::Cxn', delete $args{cxn} );
+ $params->{_factory} = sub {
+ my ( $self, $node ) = @_;
+ $cxn_class->new(
+ %args,
+ node => $node,
+ max_content_length => $self->max_content_length
+ );
+ };
+ $params->{cxn_args} = \%args;
+ $params->{cxn_class} = $cxn_class;
+ return $params;
+}
+
+#===================================
+sub new_cxn { shift->_factory->(@_) }
+#===================================
+
+1;
+
+__END__
+
+# ABSTRACT: Used by CxnPools to create new Cxn instances.
+
+=head1 DESCRIPTION
+
+This class is used by the L<Search::Elasticsearch::Role::CxnPool> implementations
+to create new L<Search::Elasticsearch::Role::Cxn>-based instances. It holds on
+to all the configuration options passed to L<Elasticsearch/new()> so
+that new Cxns can use them.
+
+It contains no user serviceable parts.
diff --git a/lib/Search/Elasticsearch/Cxn/HTTPTiny.pm b/lib/Search/Elasticsearch/Cxn/HTTPTiny.pm
new file mode 100644
index 0000000..076d0e6
--- /dev/null
+++ b/lib/Search/Elasticsearch/Cxn/HTTPTiny.pm
@@ -0,0 +1,263 @@
+# 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 Search::Elasticsearch::Cxn::HTTPTiny;
+
+use Moo;
+with 'Search::Elasticsearch::Role::Cxn', 'Search::Elasticsearch::Role::Is_Sync';
+
+use HTTP::Tiny 0.076 ();
+use namespace::clean;
+
+my $Cxn_Error = qr/ Connection.(?:timed.out|re(?:set|fused))
+ | connect:.timeout
+ | Host.is.down
+ | No.route.to.host
+ | temporarily.unavailable
+ /x;
+
+#===================================
+sub perform_request {
+#===================================
+ my ( $self, $params ) = @_;
+ my $uri = $self->build_uri($params);
+ my $method = $params->{method};
+
+ my %args;
+ if ( defined $params->{data} ) {
+ $args{content} = $params->{data};
+ $args{headers}{'Content-Type'} = $params->{mime_type};
+ $args{headers}{'Content-Encoding'} = $params->{encoding}
+ if $params->{encoding};
+ }
+
+ my $handle = $self->handle;
+ $handle->timeout( $params->{timeout} || $self->request_timeout );
+
+ my $response = $handle->request( $method, "$uri", \%args );
+
+ return $self->process_response(
+ $params, # request
+ $response->{status}, # code
+ $response->{reason}, # msg
+ $response->{content}, # body
+ $response->{headers} # headers
+ );
+}
+
+#===================================
+sub error_from_text {
+#===================================
+ local $_ = $_[2];
+ return
+ /[Tt]imed out/ ? 'Timeout'
+ : /Unexpected end of stream/ ? 'ContentLength'
+ : /SSL connection failed/ ? 'SSL'
+ : /$Cxn_Error/ ? 'Cxn'
+ : 'Request';
+}
+
+#===================================
+sub _build_handle {
+#===================================
+ my $self = shift;
+ my %args = ( default_headers => $self->default_headers );
+ if ( $self->is_https && $self->has_ssl_options ) {
+ $args{SSL_options} = $self->ssl_options;
+ if ( $args{SSL_options}{SSL_verify_mode} ) {
+ $args{verify_ssl} = 1;
+ }
+ }
+
+ return HTTP::Tiny->new( %args, %{ $self->handle_args } );
+}
+
+1;
+
+# ABSTRACT: A Cxn implementation which uses HTTP::Tiny
+
+=head1 DESCRIPTION
+
+Provides the default HTTP Cxn class and is based on L<HTTP::Tiny>.
+The HTTP::Tiny backend is fast, uses pure Perl, support proxies and https
+and provides persistent connections.
+
+This class does L<Search::Elasticsearch::Role::Cxn>, whose documentation
+provides more information, and L<Search::Elasticsearch::Role::Is_Sync>.
+
+=head1 CONFIGURATION
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<node|Search::Elasticsearch::Role::Cxn/"node">
+
+=item * L<max_content_length|Search::Elasticsearch::Role::Cxn/"max_content_length">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"gzip">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"deflate">
+
+=item * L<request_timeout|Search::Elasticsearch::Role::Cxn/"request_timeout">
+
+=item * L<ping_timeout|Search::Elasticsearch::Role::Cxn/"ping_timeout">
+
+=item * L<dead_timeout|Search::Elasticsearch::Role::Cxn/"dead_timeout">
+
+=item * L<max_dead_timeout|Search::Elasticsearch::Role::Cxn/"max_dead_timeout">
+
+=item * L<sniff_request_timeout|Search::Elasticsearch::Role::Cxn/"sniff_request_timeout">
+
+=item * L<sniff_timeout|Search::Elasticsearch::Role::Cxn/"sniff_timeout">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"handle_args">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"default_qs_params">
+
+=back
+
+=head1 SSL/TLS
+
+L<Search::Elasticsearch::Cxn::HTTPTiny> uses L<IO::Socket::SSL> to support
+HTTPS. By default, no validation of the remote host is performed.
+
+This behaviour can be changed by passing the C<ssl_options> parameter
+with any options accepted by L<IO::Socket::SSL>. For instance, to check
+that the remote host has a trusted certificate, and to avoid man-in-the-middle
+attacks, you could do the following:
+
+ use Search::Elasticsearch;
+ use IO::Socket::SSL;
+
+ my $es = Search::Elasticsearch->new(
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ SSL_verify_mode => SSL_VERIFY_PEER,
+ SSL_ca_file => '/path/to/cacert.pem'
+ }
+ );
+
+If the remote server cannot be verified, an
+L<Search::Elasticsearch::Error|SSL error> will be thrown.
+
+If you want your client to present its own certificate to the remote
+server, then use:
+
+ use Search::Elasticsearch;
+ use IO::Socket::SSL;
+
+ my $es = Search::Elasticsearch->new(
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ SSL_verify_mode => SSL_VERIFY_PEER,
+ SSL_use_cert => 1,
+ SSL_ca_file => '/path/to/cacert.pem',
+ SSL_cert_file => '/path/to/client.pem',
+ SSL_key_file => '/path/to/client.pem',
+ }
+ );
+
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+ ($status,$body) = $self->perform_request({
+ # required
+ method => 'GET|HEAD|POST|PUT|DELETE',
+ path => '/path/of/request',
+ qs => \%query_string_params,
+
+ # optional
+ data => $body_as_string,
+ mime_type => 'application/json',
+ timeout => $timeout
+ });
+
+Sends the request to the associated Elasticsearch node and returns
+a C<$status> code and the decoded response C<$body>, or throws an
+error if the request failed.
+
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<scheme()|Search::Elasticsearch::Role::Cxn/"scheme()">
+
+=item * L<is_https()|Search::Elasticsearch::Role::Cxn/"is_https()">
+
+=item * L<userinfo()|Search::Elasticsearch::Role::Cxn/"userinfo()">
+
+=item * L<default_headers()|Search::Elasticsearch::Role::Cxn/"default_headers()">
+
+=item * L<max_content_length()|Search::Elasticsearch::Role::Cxn/"max_content_length()">
+
+=item * L<build_uri()|Search::Elasticsearch::Role::Cxn/"build_uri()">
+
+=item * L<host()|Search::Elasticsearch::Role::Cxn/"host()">
+
+=item * L<port()|Search::Elasticsearch::Role::Cxn/"port()">
+
+=item * L<uri()|Search::Elasticsearch::Role::Cxn/"uri()">
+
+=item * L<is_dead()|Search::Elasticsearch::Role::Cxn/"is_dead()">
+
+=item * L<is_live()|Search::Elasticsearch::Role::Cxn/"is_live()">
+
+=item * L<next_ping()|Search::Elasticsearch::Role::Cxn/"next_ping()">
+
+=item * L<ping_failures()|Search::Elasticsearch::Role::Cxn/"ping_failures()">
+
+=item * L<mark_dead()|Search::Elasticsearch::Role::Cxn/"mark_dead()">
+
+=item * L<mark_live()|Search::Elasticsearch::Role::Cxn/"mark_live()">
+
+=item * L<force_ping()|Search::Elasticsearch::Role::Cxn/"force_ping()">
+
+=item * L<pings_ok()|Search::Elasticsearch::Role::Cxn/"pings_ok()">
+
+=item * L<sniff()|Search::Elasticsearch::Role::Cxn/"sniff()">
+
+=item * L<process_response()|Search::Elasticsearch::Role::Cxn/"process_response()">
+
+=back
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Role::Cxn>
+
+=item * L<Search::Elasticsearch::Cxn::LWP>
+
+=item * L<Search::Elasticsearch::Cxn::NetCurl>
+
+=back
+
+
diff --git a/lib/Search/Elasticsearch/Cxn/LWP.pm b/lib/Search/Elasticsearch/Cxn/LWP.pm
new file mode 100644
index 0000000..61c68f8
--- /dev/null
+++ b/lib/Search/Elasticsearch/Cxn/LWP.pm
@@ -0,0 +1,274 @@
+# 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 Search::Elasticsearch::Cxn::LWP;
+
+use Moo;
+with 'Search::Elasticsearch::Role::Cxn', 'Search::Elasticsearch::Role::Is_Sync';
+
+use LWP::UserAgent();
+use HTTP::Headers();
+use HTTP::Request();
+
+my $Cxn_Error = qr/
+ Can't.connect
+ | Server.closed.connection
+ | Connection.refused
+ /x;
+
+use namespace::clean;
+
+#===================================
+sub perform_request {
+#===================================
+ my ( $self, $params ) = @_;
+ my $uri = $self->build_uri($params);
+ my $method = $params->{method};
+
+ my %headers;
+ if ( $params->{data} ) {
+ $headers{'Content-Type'} = $params->{mime_type};
+ $headers{'Content-Encoding'} = $params->{encoding}
+ if $params->{encoding};
+ }
+ my $request = HTTP::Request->new(
+ $method => $uri,
+ [ %headers, %{ $self->default_headers }, ],
+ $params->{data}
+ );
+ my $ua = $self->handle;
+ my $timeout = $params->{timeout} || $self->request_timeout;
+ if ( $timeout ne $ua->timeout ) {
+ $ua->conn_cache->drop;
+ $ua->timeout($timeout);
+ }
+ my $response = $ua->request($request);
+
+ return $self->process_response(
+ $params, # request
+ $response->code, # code
+ $response->message, # msg
+ $response->content, # body
+ $response->headers # headers
+ );
+}
+
+#===================================
+sub error_from_text {
+#===================================
+ local $_ = $_[2];
+
+ return
+ /read timeout/ ? 'Timeout'
+ : /write failed: Connection reset by peer/ ? 'ContentLength'
+ : /$Cxn_Error/ ? 'Cxn'
+ : 'Request';
+}
+
+#===================================
+sub _build_handle {
+#===================================
+ my $self = shift;
+ my %args = (
+ keep_alive => 1,
+ parse_head => 0
+ );
+ if ( $self->is_https ) {
+ $args{ssl_opts}
+ = $self->has_ssl_options
+ ? $self->ssl_options
+ : { verify_hostname => 0, SSL_verify_mode => 0x00 };
+ }
+ return LWP::UserAgent->new( %args, %{ $self->handle_args } );
+}
+
+1;
+
+# ABSTRACT: A Cxn implementation which uses LWP
+
+=head1 DESCRIPTION
+
+Provides an HTTP Cxn class and based on L<LWP>.
+The LWP backend uses pure Perl and persistent connections.
+
+This class does L<Search::Elasticsearch::Role::Cxn>, whose documentation
+provides more information, and L<Search::Elasticsearch::Role::Is_Sync>.
+
+=head1 CONFIGURATION
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<node|Search::Elasticsearch::Role::Cxn/"node">
+
+=item * L<max_content_length|Search::Elasticsearch::Role::Cxn/"max_content_length">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"gzip">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"deflate">
+
+=item * L<request_timeout|Search::Elasticsearch::Role::Cxn/"request_timeout">
+
+=item * L<ping_timeout|Search::Elasticsearch::Role::Cxn/"ping_timeout">
+
+=item * L<dead_timeout|Search::Elasticsearch::Role::Cxn/"dead_timeout">
+
+=item * L<max_dead_timeout|Search::Elasticsearch::Role::Cxn/"max_dead_timeout">
+
+=item * L<sniff_request_timeout|Search::Elasticsearch::Role::Cxn/"sniff_request_timeout">
+
+=item * L<sniff_timeout|Search::Elasticsearch::Role::Cxn/"sniff_timeout">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"handle_args">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"default_qs_params">
+
+=back
+
+=head1 SSL/TLS
+
+L<Search::Elasticsearch::Cxn::LWP> uses L<IO::Socket::SSL> to support
+HTTPS. By default, no validation of the remote host is performed.
+
+This behaviour can be changed by passing the C<ssl_options> parameter
+with any options accepted by L<IO::Socket::SSL>. For instance, to check
+that the remote host has a trusted certificate, and to avoid man-in-the-middle
+attacks, you could do the following:
+
+ use Search::Elasticsearch;
+
+ my $es = Search::Elasticsearch->new(
+ cxn => 'LWP',
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ verify_hostname => 1,
+ SSL_ca_file => '/path/to/cacert.pem'
+ }
+ );
+
+If the remote server cannot be verified, an
+L<Search::Elasticsearch::Error|Cxn error> will be thrown - LWP does not
+allow us to detect that the connection error was due to invalid SSL.
+
+If you want your client to present its own certificate to the remote
+server, then use:
+
+ use Search::Elasticsearch;
+
+ my $es = Search::Elasticsearch->new(
+ cxn => 'LWP',
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ verify_hostname => 1,
+ SSL_ca_file => '/path/to/cacert.pem',
+ SSL_use_cert => 1,
+ SSL_cert_file => '/path/to/client.pem',
+ SSL_key_file => '/path/to/client.pem',
+ }
+ );
+
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+ ($status,$body) = $self->perform_request({
+ # required
+ method => 'GET|HEAD|POST|PUT|DELETE',
+ path => '/path/of/request',
+ qs => \%query_string_params,
+
+ # optional
+ data => $body_as_string,
+ mime_type => 'application/json',
+ timeout => $timeout
+ });
+
+Sends the request to the associated Elasticsearch node and returns
+a C<$status> code and the decoded response C<$body>, or throws an
+error if the request failed.
+
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<scheme()|Search::Elasticsearch::Role::Cxn/"scheme()">
+
+=item * L<is_https()|Search::Elasticsearch::Role::Cxn/"is_https()">
+
+=item * L<userinfo()|Search::Elasticsearch::Role::Cxn/"userinfo()">
+
+=item * L<default_headers()|Search::Elasticsearch::Role::Cxn/"default_headers()">
+
+=item * L<max_content_length()|Search::Elasticsearch::Role::Cxn/"max_content_length()">
+
+=item * L<build_uri()|Search::Elasticsearch::Role::Cxn/"build_uri()">
+
+=item * L<host()|Search::Elasticsearch::Role::Cxn/"host()">
+
+=item * L<port()|Search::Elasticsearch::Role::Cxn/"port()">
+
+=item * L<uri()|Search::Elasticsearch::Role::Cxn/"uri()">
+
+=item * L<is_dead()|Search::Elasticsearch::Role::Cxn/"is_dead()">
+
+=item * L<is_live()|Search::Elasticsearch::Role::Cxn/"is_live()">
+
+=item * L<next_ping()|Search::Elasticsearch::Role::Cxn/"next_ping()">
+
+=item * L<ping_failures()|Search::Elasticsearch::Role::Cxn/"ping_failures()">
+
+=item * L<mark_dead()|Search::Elasticsearch::Role::Cxn/"mark_dead()">
+
+=item * L<mark_live()|Search::Elasticsearch::Role::Cxn/"mark_live()">
+
+=item * L<force_ping()|Search::Elasticsearch::Role::Cxn/"force_ping()">
+
+=item * L<pings_ok()|Search::Elasticsearch::Role::Cxn/"pings_ok()">
+
+=item * L<sniff()|Search::Elasticsearch::Role::Cxn/"sniff()">
+
+=item * L<process_response()|Search::Elasticsearch::Role::Cxn/"process_response()">
+
+=back
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Role::Cxn>
+
+=item * L<Search::Elasticsearch::Cxn::HTTPTiny>
+
+=item * L<Search::Elasticsearch::Cxn::NetCurl>
+
+=back
+
+
+
diff --git a/lib/Search/Elasticsearch/Cxn/Mojo.pm b/lib/Search/Elasticsearch/Cxn/Mojo.pm
new file mode 100644
index 0000000..23bd25c
--- /dev/null
+++ b/lib/Search/Elasticsearch/Cxn/Mojo.pm
@@ -0,0 +1,292 @@
+# 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 Search::Elasticsearch::Cxn::Mojo;
+
+use Mojo::UserAgent();
+use Promises qw(deferred);
+use Try::Tiny;
+use Moo;
+
+with 'Search::Elasticsearch::Role::Cxn::Async';
+with 'Search::Elasticsearch::Role::Cxn',
+ 'Search::Elasticsearch::Role::Is_Async';
+
+has 'connect_timeout' => ( is => 'ro', default => 2 );
+
+use namespace::clean;
+
+#===================================
+sub perform_request {
+#===================================
+ my ( $self, $params ) = @_;
+
+ my $uri = $self->build_uri($params) . '';
+ my $method = $params->{method};
+ my %headers = ( %{ $self->default_headers } );
+
+ my @args = ( $method, $uri, \%headers );
+ my $data = $params->{data};
+ if ( defined $data ) {
+ $headers{'Content-Type'} = $params->{mime_type};
+ $headers{'Content-Encoding'} = $params->{encoding}
+ if $params->{encoding};
+ push @args, $data;
+ }
+
+ my $handle = $self->handle;
+ $handle->connect_timeout( $self->connect_timeout );
+ $handle->request_timeout( $params->{timeout} || $self->request_timeout );
+
+ my $tx = $handle->build_tx(@args);
+
+ my $deferred = deferred;
+ $tx = $handle->start(
+ $tx,
+ sub {
+ my ( $ua, $tx ) = @_;
+ my $res = $tx->res;
+ my $error;
+ if ( $error = $res->error ) {
+ $error = $error->{message}
+ if ref $error eq 'HASH';
+ }
+
+ my $headers = $res->headers->to_hash;
+ $headers->{ lc($_) } = delete $headers->{$_} for keys %{$headers};
+ try {
+ my ( $code, $response ) = $self->process_response(
+ $params, # request
+ ( $res->code || 500 ), # status
+ $error, # reason
+ $res->body, # content
+ $headers, # headers
+ );
+ $deferred->resolve( $code, $response );
+ }
+ catch {
+ $deferred->reject($_);
+ };
+ }
+ );
+ $deferred->promise;
+}
+
+#===================================
+sub error_from_text {
+#===================================
+ local $_ = $_[2];
+ return
+ /[Tt]imed out/ ? 'Timeout'
+ : /SSL connect attempt failed/ ? 'SSL'
+ : /Invalid argument/ ? 'Cxn'
+ : 'Request';
+}
+
+#===================================
+sub _build_handle {
+#===================================
+ my $self = shift;
+ my %args = %{ $self->handle_args };
+ if ( $self->is_https && $self->has_ssl_options ) {
+ %args = ( %args, %{ $self->ssl_options } );
+ }
+ return Mojo::UserAgent->new(%args);
+}
+1;
+
+# ABSTRACT: An async Cxn implementation which uses Mojo::UserAgent
+
+=head1 DESCRIPTION
+
+Provides an async HTTP Cxn class based on L<Mojo::UserAgent>.
+The Mojo backend is fast, uses pure Perl, support proxies and https
+and provides persistent connections.
+
+This class does L<Search::Elasticsearch::Role::Cxn>, whose documentation
+provides more information, L<Search::Elasticsearch::Role::Async::Cxn>,
+and L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 CONFIGURATION
+
+=head2 C<connect_timeout>
+
+Unlike most HTTP backends, L<Mojo::UserAgent> accepts a separate C<connect_timeout>
+parameter, which defaults to C<2> seconds but can be reduced in an
+environment with low network latency.
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<node|Search::Elasticsearch::Role::Cxn/"node">
+
+=item * L<max_content_length|Search::Elasticsearch::Role::Cxn/"max_content_length">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"gzip">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"deflate">
+
+=item * L<request_timeout|Search::Elasticsearch::Role::Cxn/"request_timeout">
+
+=item * L<ping_timeout|Search::Elasticsearch::Role::Cxn/"ping_timeout">
+
+=item * L<dead_timeout|Search::Elasticsearch::Role::Cxn/"dead_timeout">
+
+=item * L<max_dead_timeout|Search::Elasticsearch::Role::Cxn/"max_dead_timeout">
+
+=item * L<sniff_request_timeout|Search::Elasticsearch::Role::Cxn/"sniff_request_timeout">
+
+=item * L<sniff_timeout|Search::Elasticsearch::Role::Cxn/"sniff_timeout">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"handle_args">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"default_qs_params">
+
+=back
+
+=head1 SSL/TLS
+
+L<Search::Elasticsearch::Cxn::Mojo> does no validation of the remote host by default.
+
+This behaviour can be changed by passing the C<ssl_options> parameter
+with the C<ca>, C<cert>, and C<key> options. For instance, to check
+that the remote host has a trusted certificate, and to avoid man-in-the-middle
+attacks, you could do the following:
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new(
+ cxn => 'Mojo',
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ ca => '/path/to/cacert.pem'
+ }
+ );
+
+If the remote server cannot be verified, an
+L<Search::Elasticsearch::Error|SSL error> will be thrown.
+
+If you want your client to present its own certificate to the remote
+server, then use:
+
+ use Search::Elasticsearch::Async;
+
+ my $es = Search::Elasticsearch::Async->new(
+ cxn => 'Mojo',
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ ca => '/path/to/cacert.pem'
+ cert => '/path/to/client.pem',
+ key => '/path/to/client.pem'
+ }
+ );
+
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+ $self->perform_request({
+ # required
+ method => 'GET|HEAD|POST|PUT|DELETE',
+ path => '/path/of/request',
+ qs => \%query_string_params,
+
+ # optional
+ data => $body_as_string,
+ mime_type => 'application/json',
+ timeout => $timeout
+ })
+ ->then(sub { my ($status,body) = @_; ...})
+
+Sends the request to the associated Elasticsearch node and returns
+a C<$status> code and the decoded response C<$body>, or throws an
+error if the request failed.
+
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<scheme()|Search::Elasticsearch::Role::Cxn/"scheme()">
+
+=item * L<is_https()|Search::Elasticsearch::Role::Cxn/"is_https()">
+
+=item * L<userinfo()|Search::Elasticsearch::Role::Cxn/"userinfo()">
+
+=item * L<default_headers()|Search::Elasticsearch::Role::Cxn/"default_headers()">
+
+=item * L<max_content_length()|Search::Elasticsearch::Role::Cxn/"max_content_length()">
+
+=item * L<build_uri()|Search::Elasticsearch::Role::Cxn/"build_uri()">
+
+=item * L<host()|Search::Elasticsearch::Role::Cxn/"host()">
+
+=item * L<port()|Search::Elasticsearch::Role::Cxn/"port()">
+
+=item * L<uri()|Search::Elasticsearch::Role::Cxn/"uri()">
+
+=item * L<is_dead()|Search::Elasticsearch::Role::Cxn/"is_dead()">
+
+=item * L<is_live()|Search::Elasticsearch::Role::Cxn/"is_live()">
+
+=item * L<next_ping()|Search::Elasticsearch::Role::Cxn/"next_ping()">
+
+=item * L<ping_failures()|Search::Elasticsearch::Role::Cxn/"ping_failures()">
+
+=item * L<mark_dead()|Search::Elasticsearch::Role::Cxn/"mark_dead()">
+
+=item * L<mark_live()|Search::Elasticsearch::Role::Cxn/"mark_live()">
+
+=item * L<force_ping()|Search::Elasticsearch::Role::Cxn/"force_ping()">
+
+=item * L<pings_ok()|Search::Elasticsearch::Role::Cxn/"pings_ok()">
+
+=item * L<sniff()|Search::Elasticsearch::Role::Cxn/"sniff()">
+
+=item * L<process_response()|Search::Elasticsearch::Role::Cxn/"process_response()">
+
+=back
+
+From L<Search::Elasticsearch::Role::Async::Cxn>
+
+=over
+
+=item * L<pings_ok()|Search::Elasticsearch::Role::Cxn/"pings_ok()">
+
+=item * L<sniff()|Search::Elasticsearch::Role::Cxn/"sniff()">
+
+=back
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Role::Cxn::AEHTTP>
+
+=back
diff --git a/lib/Search/Elasticsearch/Cxn/NetCurl.pm b/lib/Search/Elasticsearch/Cxn/NetCurl.pm
new file mode 100644
index 0000000..30489c9
--- /dev/null
+++ b/lib/Search/Elasticsearch/Cxn/NetCurl.pm
@@ -0,0 +1,383 @@
+# 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 Search::Elasticsearch::Cxn::NetCurl;
+
+use Moo;
+with 'Search::Elasticsearch::Role::Cxn', 'Search::Elasticsearch::Role::Is_Sync';
+
+use Search::Elasticsearch 8.00;
+our $VERSION = '8.00';
+
+use HTTP::Parser::XS qw(HEADERS_AS_HASHREF parse_http_response);
+use Try::Tiny;
+use Net::Curl::Easy qw(
+ CURLOPT_HEADER
+ CURLOPT_VERBOSE
+ CURLOPT_URL
+ CURLOPT_CONNECTTIMEOUT_MS
+ CURLOPT_CUSTOMREQUEST
+ CURLOPT_TIMEOUT_MS
+ CURLOPT_POSTFIELDS
+ CURLOPT_POSTFIELDSIZE
+ CURLOPT_HTTPHEADER
+ CURLOPT_SSL_VERIFYPEER
+ CURLOPT_SSL_VERIFYHOST
+ CURLOPT_WRITEDATA
+ CURLOPT_HEADERDATA
+ CURLINFO_RESPONSE_CODE
+ CURLOPT_TCP_NODELAY
+ CURLOPT_NOBODY
+);
+
+has 'connect_timeout' => ( is => 'ro', default => 2 );
+
+use namespace::clean;
+
+#===================================
+sub perform_request {
+#===================================
+ my ( $self, $params ) = @_;
+ my $uri = $self->build_uri($params);
+ my $method = $params->{method};
+
+ my $handle = $self->handle;
+ $handle->reset;
+
+ # $handle->setopt( CURLOPT_VERBOSE, 1 );
+
+ $handle->setopt( CURLOPT_HEADER, 0 );
+ $handle->setopt( CURLOPT_TCP_NODELAY, 1 );
+ $handle->setopt( CURLOPT_URL, $uri );
+ $handle->setopt( CURLOPT_CUSTOMREQUEST, $method );
+ $handle->setopt( CURLOPT_NOBODY, 1 ) if $method eq 'HEAD';
+
+ $handle->setopt( CURLOPT_CONNECTTIMEOUT_MS, $self->connect_timeout * 1000 );
+ $handle->setopt( CURLOPT_TIMEOUT_MS,
+ 1000 * ( $params->{timeout} || $self->request_timeout ) );
+
+ my %headers = %{ $self->default_headers };
+
+ my $data = $params->{data};
+ if ( defined $data ) {
+ $headers{'Content-Type'} = $params->{mime_type};
+ $headers{'Expect'} = '';
+ $headers{'Content-Encoding'} = $params->{encoding}
+ if $params->{encoding};
+ $handle->setopt( CURLOPT_POSTFIELDS, $data );
+ $handle->setopt( CURLOPT_POSTFIELDSIZE, length $data );
+ }
+
+ $handle->setopt( CURLOPT_HTTPHEADER,
+ [ map { "$_: " . $headers{$_} } keys %headers ] )
+ if %headers;
+
+ my %opts = %{ $self->handle_args };
+ if ( $self->is_https ) {
+ if ( $self->has_ssl_options ) {
+ %opts = ( %opts, %{ $self->ssl_options } );
+ }
+ else {
+ %opts = (
+ %opts,
+ ( CURLOPT_SSL_VERIFYPEER() => 0,
+ CURLOPT_SSL_VERIFYHOST() => 0
+ )
+ );
+ }
+ }
+
+ for ( keys %opts ) {
+ $handle->setopt( $_, $opts{$_} );
+ }
+
+ my $content = my $head = '';
+ $handle->setopt( CURLOPT_WRITEDATA, \$content );
+ $handle->setopt( CURLOPT_HEADERDATA, \$head );
+
+ my ( $code, $msg, $headers );
+
+ try {
+ $handle->perform;
+ ( undef, undef, $code, $msg, $headers )
+ = parse_http_response( $head, HEADERS_AS_HASHREF );
+ }
+ catch {
+ $code = 509;
+ $msg = ( 0 + $_ ) . ": $_";
+ $msg . ", " . $handle->error
+ if $handle->error;
+ undef $content;
+ };
+
+ return $self->process_response(
+ $params, # request
+ $code, # code
+ $msg, # msg
+ $content, # body
+ $headers # headers
+ );
+}
+
+#===================================
+sub error_from_text {
+#===================================
+ local $_ = $_[2];
+ shift;
+ return
+ m/^7:/ ? 'Cxn'
+ : m/^28:/ ? 'Timeout'
+ : m/^51:/ ? 'SSL'
+ : m/^55:/ ? 'ContentLength'
+ : 'Request';
+
+}
+
+#===================================
+sub _build_handle { Net::Curl::Easy->new }
+#===================================
+
+1;
+
+# ABSTRACT: A Cxn implementation which uses libcurl via Net::Curl
+
+=head1 DESCRIPTION
+
+Provides an HTTP Cxn class based on L<Net::Curl>.
+The C<NetCurl> Cxn class is very fast and uses persistent connections but
+requires XS and C<libcurl>.
+
+This class does L<Search::Elasticsearch::Role::Cxn>, whose documentation
+provides more information.
+
+=head1 CONFIGURATION
+
+=head2 C<connect_timeout>
+
+Unlike most HTTP backends, L<Net::Curl> accepts a separate C<connect_timeout>
+parameter, which defaults to C<2> seconds but can be reduced in an
+environment with low network latency.
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<node|Search::Elasticsearch::Role::Cxn/"node">
+
+=item * L<max_content_length|Search::Elasticsearch::Role::Cxn/"max_content_length">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"gzip">
+
+=item * L<deflate|Search::Elasticsearch::Role::Cxn/"deflate">
+
+=item * L<request_timeout|Search::Elasticsearch::Role::Cxn/"request_timeout">
+
+=item * L<ping_timeout|Search::Elasticsearch::Role::Cxn/"ping_timeout">
+
+=item * L<dead_timeout|Search::Elasticsearch::Role::Cxn/"dead_timeout">
+
+=item * L<max_dead_timeout|Search::Elasticsearch::Role::Cxn/"max_dead_timeout">
+
+=item * L<sniff_request_timeout|Search::Elasticsearch::Role::Cxn/"sniff_request_timeout">
+
+=item * L<sniff_timeout|Search::Elasticsearch::Role::Cxn/"sniff_timeout">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"handle_args">
+
+=item * L<handle_args|Search::Elasticsearch::Role::Cxn/"default_qs_params">
+
+=back
+
+=head1 SSL/TLS
+
+L<Search::Elasticsearch::Cxn::NetCurl> does no validation of remote
+hosts by default.
+
+This behaviour can be changed by passing the C<ssl_options> parameter
+with any options accepted by L<Net::Curl> (see L<http://curl.haxx.se/libcurl/c/curl_easy_setopt.html>).
+
+For instance, to check that the remote host has a trusted certificate,
+and to avoid man-in-the-middle attacks, you could do the following:
+
+ use Search::Elasticsearch;
+ use Net::Curl::Easy qw(
+ CURLOPT_CAINFO
+ );
+
+ my $es = Search::Elasticsearch->new(
+ cxn => 'NetCurl',
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ CURLOPT_CAINFO() => '/path/to/cacert.pem'
+ }
+ );
+
+If the remote server cannot be verified, an
+L<Search::Elasticsearch::Error|SSL error> will be thrown.
+
+If you want your client to present its own certificate to the remote
+server, then use:
+
+ use Net::Curl::Easy qw(
+ CURLOPT_CAINFO
+ CURLOPT_SSLCERT
+ CURLOPT_SSLKEY
+ );
+
+ my $es = Search::Elasticsearch->new(
+ cxn => 'NetCurl',
+ nodes => [
+ "https://node1.mydomain.com:9200",
+ "https://node2.mydomain.com:9200",
+ ],
+ ssl_options => {
+ CURLOPT_CAINFO() => '/path/to/cacert.pem'
+ CURLOPT_SSLCERT() => '/path/to/client.pem',
+ CURLOPT_SSLKEY() => '/path/to/client.pem',
+ }
+ );
+
+
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+ ($status,$body) = $self->perform_request({
+ # required
+ method => 'GET|HEAD|POST|PUT|DELETE',
+ path => '/path/of/request',
+ qs => \%query_string_params,
+
+ # optional
+ data => $body_as_string,
+ mime_type => 'application/json',
+ timeout => $timeout
+ });
+
+Sends the request to the associated Elasticsearch node and returns
+a C<$status> code and the decoded response C<$body>, or throws an
+error if the request failed.
+
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::Cxn>
+
+=over
+
+=item * L<scheme()|Search::Elasticsearch::Role::Cxn/"scheme()">
+
+=item * L<is_https()|Search::Elasticsearch::Role::Cxn/"is_https()">
+
+=item * L<userinfo()|Search::Elasticsearch::Role::Cxn/"userinfo()">
+
+=item * L<default_headers()|Search::Elasticsearch::Role::Cxn/"default_headers()">
+
+=item * L<max_content_length()|Search::Elasticsearch::Role::Cxn/"max_content_length()">
+
+=item * L<build_uri()|Search::Elasticsearch::Role::Cxn/"build_uri()">
+
+=item * L<host()|Search::Elasticsearch::Role::Cxn/"host()">
+
+=item * L<port()|Search::Elasticsearch::Role::Cxn/"port()">
+
+=item * L<uri()|Search::Elasticsearch::Role::Cxn/"uri()">
+
+=item * L<is_dead()|Search::Elasticsearch::Role::Cxn/"is_dead()">
+
+=item * L<is_live()|Search::Elasticsearch::Role::Cxn/"is_live()">
+
+=item * L<next_ping()|Search::Elasticsearch::Role::Cxn/"next_ping()">
+
+=item * L<ping_failures()|Search::Elasticsearch::Role::Cxn/"ping_failures()">
+
+=item * L<mark_dead()|Search::Elasticsearch::Role::Cxn/"mark_dead()">
+
+=item * L<mark_live()|Search::Elasticsearch::Role::Cxn/"mark_live()">
+
+=item * L<force_ping()|Search::Elasticsearch::Role::Cxn/"force_ping()">
+
+=item * L<pings_ok()|Search::Elasticsearch::Role::Cxn/"pings_ok()">
+
+=item * L<sniff()|Search::Elasticsearch::Role::Cxn/"sniff()">
+
+=item * L<process_response()|Search::Elasticsearch::Role::Cxn/"process_response()">
+
+=back
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Role::Cxn>
+
+=item * L<Search::Elasticsearch::Cxn::LWP>
+
+=item * L<Search::Elasticsearch::Cxn::HTTPTiny>
+
+=back
+
+=head1 BUGS
+
+This is a stable API but this implemenation is new. Watch this space
+for new releases.
+
+If you have any suggestions for improvements, or find any bugs, please report
+them to L<http://github.com/elasticsearch/elasticsearch-perl/issues>.
+I will be notified, and then you'll automatically be notified of progress on
+your bug as I make changes.
+
+=head1 SUPPORT
+
+You can find documentation for this module with the perldoc command.
+
+ perldoc Search::Elasticsearch::Cxn::NetCurl
+
+You can also look for information at:
+
+=over 4
+
+=item * GitHub
+
+L<http://github.com/elasticsearch/elasticsearch-perl>
+
+=item * CPAN Ratings
+
+L<http://cpanratings.perl.org/d/Search::Elasticsearch::Cxn::NetCurl>
+
+
+=item * Search MetaCPAN
+
+L<https://metacpan.org/module/Search::Elasticsearch::Cxn::NetCurl>
+
+=item * IRC
+
+The L<#elasticsearch|irc://irc.freenode.net/elasticsearch> channel on
+C<irc.freenode.net>.
+
+=item * Mailing list
+
+The main L<Elasticsearch mailing list|http://www.elastic.co/community>.
+
+=back
+
diff --git a/lib/Search/Elasticsearch/CxnPool/Async/Sniff.pm b/lib/Search/Elasticsearch/CxnPool/Async/Sniff.pm
new file mode 100644
index 0000000..bb21298
--- /dev/null
+++ b/lib/Search/Elasticsearch/CxnPool/Async/Sniff.pm
@@ -0,0 +1,308 @@
+# 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 Search::Elasticsearch::CxnPool::Async::Sniff;
+
+use Moo;
+with 'Search::Elasticsearch::Role::CxnPool::Sniff',
+ 'Search::Elasticsearch::Role::Is_Async';
+
+use Scalar::Util qw(weaken);
+use Promises qw(deferred);
+use Search::Elasticsearch::Util qw(new_error);
+
+use namespace::clean;
+has 'concurrent_sniff' => ( is => 'rw', default => 4 );
+has '_current_sniff' => ( is => 'rw', clearer => '_clear_sniff' );
+
+#===================================
+sub next_cxn {
+#===================================
+ my ( $self, $no_sniff ) = @_;
+
+ return $self->sniff->then( sub { $self->next_cxn('no_sniff') } )
+ if $self->next_sniff <= time() && !$no_sniff;
+
+ my $cxns = $self->cxns;
+ my $total = @$cxns;
+ my $cxn;
+
+ while ( 0 < $total-- ) {
+ $cxn = $cxns->[ $self->next_cxn_num ];
+ last if $cxn->is_live;
+ undef $cxn;
+ }
+
+ my $deferred = deferred;
+
+ if ($cxn) {
+ $deferred->resolve($cxn);
+ }
+ else {
+ $deferred->reject(
+ new_error(
+ "NoNodes",
+ "No nodes are available: [" . $self->cxns_seeds_str . ']'
+ )
+ );
+ }
+ return $deferred->promise;
+}
+
+#===================================
+sub sniff {
+#===================================
+ my $self = shift;
+
+ my $promise;
+ if ( $promise = $self->_current_sniff ) {
+ return $promise;
+ }
+
+ my $deferred = deferred;
+ my $cxns = $self->cxns;
+ my $total = @$cxns;
+ my $done = 0;
+ my $current = 0;
+ my $done_seeds = 0;
+ $promise = $self->_current_sniff( $deferred->promise );
+
+ my ( @all, @skipped );
+
+ while ( 0 < $total-- ) {
+ my $cxn = $cxns->[ $self->next_cxn_num ];
+ if ( $cxn->is_dead ) {
+ push @skipped, $cxn;
+ }
+ else {
+ push @all, $cxn;
+ }
+ }
+
+ push @all, @skipped;
+ unless (@all) {
+ @all = $self->_seeds_as_cxns;
+ $done_seeds++;
+ }
+
+ my ( $weak_check_sniff, $cxn );
+ my $check_sniff = sub {
+
+ return if $done;
+ my ( $cxn, $nodes ) = @_;
+ if ( $nodes && $self->parse_sniff($nodes) ) {
+ $done++;
+ $self->_clear_sniff;
+ return $deferred->resolve();
+ }
+
+ unless ( @all || $done_seeds++ ) {
+ $self->logger->info("No live nodes available. Trying seed nodes.");
+ @all = $self->_seeds_as_cxns;
+ }
+
+ if ( my $cxn = shift @all ) {
+ return $cxn->sniff->done($weak_check_sniff);
+ }
+ if ( --$current == 0 ) {
+ $self->_clear_sniff;
+ $deferred->resolve();
+ }
+ };
+ weaken( $weak_check_sniff = $check_sniff );
+
+ for ( 1 .. $self->concurrent_sniff ) {
+ my $cxn = shift(@all) || last;
+ $current++;
+ $cxn->sniff->done($check_sniff);
+ }
+
+ return $promise;
+}
+
+#===================================
+sub _seeds_as_cxns {
+#===================================
+ my $self = shift;
+ my $factory = $self->cxn_factory;
+ return map { $factory->new_cxn($_) } @{ $self->seed_nodes };
+}
+
+1;
+
+# ABSTRACT: An async CxnPool for connecting to a local cluster with a dynamic node list
+
+=head1 SYNOPSIS
+
+
+ $e = Search::Elasticsearch::Async->new(
+ cxn_pool => 'Async::Sniff',
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ],
+ );
+
+=head1 DESCRIPTION
+
+The L<Async::Sniff|Search::Elasticsearch::CxnPool::Async::Sniff> connection
+pool should be used when you B<do> have direct access to the Elasticsearch
+cluster, eg when your web servers and Elasticsearch servers are on the same
+network. The nodes that you specify are used to I<discover> the cluster,
+which is then I<sniffed> to find the current list of live nodes that the
+cluster knows about.
+
+This sniff process is repeated regularly, or whenever a node fails,
+to update the list of healthy nodes. So if you add more nodes to your
+cluster, they will be auto-discovered during a sniff.
+
+If all sniffed nodes fail, then it falls back to sniffing the original
+I<seed> nodes that you specified in C<new()>.
+
+For L<HTTP Cxn classes|Search::Elasticsearch::Role::Cxn>, this module
+will also dynamically detect the C<max_content_length> which the nodes
+in the cluster will accept.
+
+This class does L<Search::Elasticsearch::Role::CxnPool::Sniff> and
+L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 CONFIGURATION
+
+=head2 C<nodes>
+
+The list of nodes to use to discover the cluster. Can accept a single node,
+multiple nodes, and defaults to C<localhost:9200> if no C<nodes> are
+specified. See L<Search::Elasticsearch::Role::Cxn/node> for details of the node
+specification.
+
+=head2 C<concurrent_sniff>
+
+By default, this module will issue up to 4 concurrent sniff requests in parallel,
+depending on how many nodes are known. The first successful response is used
+to set the new list of live nodes. Set C<concurrent_sniff> to change the
+maximum number of concurrent sniff requests.
+
+=head2 See also
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/request_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/sniff_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/sniff_request_timeout>
+
+=back
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::CxnPool::Sniff>
+
+=over
+
+=item * L<sniff_interval|Search::Elasticsearch::Role::CxnPool::Sniff/"sniff_interval">
+
+=item * L<sniff_max_content_length|Search::Elasticsearch::Role::CxnPool::Sniff/"sniff_max_content_length">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<randomize_cxns|Search::Elasticsearch::Role::CxnPool/"randomize_cxns">
+
+=back
+
+=head1 METHODS
+
+=head2 C<next_cxn()>
+
+ $cxn_pool->next_cxn
+ -> then( sub { my $cxn = shift })
+
+Returns the next available live node (in round robin fashion), or
+throws a C<NoNodes> error if no nodes can be sniffed from the cluster.
+
+=head2 C<sniff()>
+
+ $cxn_pool->sniff->then(
+ sub { "ok" },
+ sub { "not_ok" }
+ );
+
+Sniffs the cluster and returns a promise which is resolved on success, or
+rejected on failure.
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::CxnPool::Sniff>
+
+=over
+
+=item * L<schedule_check()|Search::Elasticsearch::Role::CxnPool::Sniff/"schedule_check()">
+
+=item * L<parse_sniff()|Search::Elasticsearch::Role::CxnPool::Sniff/"parse_sniff()">
+
+=item * L<should_accept_node()|Search::Elasticsearch::Role::CxnPool::Sniff/"should_accept_node()">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<cxn_factory()|Search::Elasticsearch::Role::CxnPool/"cxn_factory()">
+
+=item * L<logger()|Search::Elasticsearch::Role::CxnPool/"logger()">
+
+=item * L<serializer()|Search::Elasticsearch::Role::CxnPool/"serializer()">
+
+=item * L<current_cxn_num()|Search::Elasticsearch::Role::CxnPool/"current_cxn_num()">
+
+=item * L<cxns()|Search::Elasticsearch::Role::CxnPool/"cxns()">
+
+=item * L<seed_nodes()|Search::Elasticsearch::Role::CxnPool/"seed_nodes()">
+
+=item * L<next_cxn_num()|Search::Elasticsearch::Role::CxnPool/"next_cxn_num()">
+
+=item * L<set_cxns()|Search::Elasticsearch::Role::CxnPool/"set_cxns()">
+
+=item * L<request_ok()|Search::Elasticsearch::Role::CxnPool/"request_ok()">
+
+=item * L<request_failed()|Search::Elasticsearch::Role::CxnPool/"request_failed()">
+
+=item * L<should_retry()|Search::Elasticsearch::Role::CxnPool/"should_retry()">
+
+=item * L<should_mark_dead()|Search::Elasticsearch::Role::CxnPool/"should_mark_dead()">
+
+=item * L<cxns_str()|Search::Elasticsearch::Role::CxnPool/"cxns_str()">
+
+=item * L<cxns_seeds_str()|Search::Elasticsearch::Role::CxnPool/"cxns_seeds_str()">
+
+=item * L<retries()|Search::Elasticsearch::Role::CxnPool/"retries()">
+
+=item * L<reset_retries()|Search::Elasticsearch::Role::CxnPool/"reset_retries()">
+
+=back
+
diff --git a/lib/Search/Elasticsearch/CxnPool/Async/Static.pm b/lib/Search/Elasticsearch/CxnPool/Async/Static.pm
new file mode 100644
index 0000000..873c2ad
--- /dev/null
+++ b/lib/Search/Elasticsearch/CxnPool/Async/Static.pm
@@ -0,0 +1,213 @@
+# 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 Search::Elasticsearch::CxnPool::Async::Static;
+
+use Moo;
+with 'Search::Elasticsearch::Role::CxnPool::Static',
+ 'Search::Elasticsearch::Role::Is_Async';
+
+use Search::Elasticsearch::Util qw(new_error);
+use Scalar::Util qw(weaken);
+use Promises qw(deferred);
+use namespace::clean;
+
+#===================================
+sub next_cxn {
+#===================================
+ my ($self) = @_;
+
+ my $cxns = $self->cxns;
+ my $now = time();
+ my $deferred = deferred;
+
+ my ( %seen, @skipped, $weak_find_cxn );
+
+ my $find_cxn = sub {
+ my $total = @$cxns;
+ my $found;
+
+ if ( $total > keys %seen ) {
+
+ # we haven't seen all cxns yet
+ while ( $total-- ) {
+ my $cxn = $cxns->[ $self->next_cxn_num ];
+ next if $seen{$cxn}++;
+
+ return $deferred->resolve($cxn)
+ if $cxn->is_live;
+
+ if ( $cxn->next_ping <= time() ) {
+ $found = $cxn;
+ last;
+ }
+
+ push @skipped, $cxn;
+ }
+ }
+
+ if ( $found ||= shift @skipped ) {
+ return $found->pings_ok->then(
+ sub { $deferred->resolve($found) }, # success
+ $weak_find_cxn # resolve
+ );
+ }
+
+ $_->force_ping for @$cxns;
+
+ return $deferred->reject(
+ new_error(
+ "NoNodes", "No nodes are available: [" . $self->cxns_str . ']'
+ )
+ );
+
+ };
+ weaken( $weak_find_cxn = $find_cxn );
+
+ $find_cxn->();
+ $deferred->promise;
+}
+
+1;
+
+# ABSTRACT: An async CxnPool for connecting to a remote cluster with a static list of nodes.
+
+=head1 SYNOPSIS
+
+
+ $e = Search::Elasticsearch::Async->new(
+ cxn_pool => 'Async::Static' # default
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ],
+ );
+
+=head1 DESCRIPTION
+
+The L<Async::Static|Search::Elasticsearch::CxnPool::Async::Static> connection
+pool, which is the default, should be used when you don't have direct access
+to the Elasticsearch cluster, eg when you are accessing the cluster through a
+proxy. It round-robins through the nodes that you specified, and pings each
+node before it is used for the first time, to ensure that it is responding.
+
+If any node fails, then all nodes are pinged before the next request to
+ensure that they are still alive and responding. Failed nodes will be
+pinged regularly to check if they have recovered.
+
+This class does L<Search::Elasticsearch::Role::CxnPool::Async::Static> and
+L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 CONFIGURATION
+
+=head2 C<nodes>
+
+The list of nodes to use to serve requests. Can accept a single node,
+multiple nodes, and defaults to C<localhost:9200> if no C<nodes> are
+specified. See L<Search::Elasticsearch::Role::Cxn/node> for details of the node
+specification.
+
+=head2 See also
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/request_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/ping_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/dead_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/max_dead_timeout>
+
+=back
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<randomize_cxns|Search::Elasticsearch::Role::CxnPool/"randomize_cxns">
+
+=back
+
+
+=head1 METHODS
+
+=head2 C<next_cxn()>
+
+ $cxn_pool->next_cxn
+ ->then( sub { my $cxn = shift });
+
+Returns the next available live node (in round robin fashion), or
+throws a C<NoNodes> error if no nodes respond to ping requests.
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::CxnPool::Static>
+
+=over
+
+=item * L<schedule_check()|Search::Elasticsearch::Role::CxnPool::Static/"schedule_check()">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<cxn_factory()|Search::Elasticsearch::Role::CxnPool/"cxn_factory()">
+
+=item * L<logger()|Search::Elasticsearch::Role::CxnPool/"logger()">
+
+=item * L<serializer()|Search::Elasticsearch::Role::CxnPool/"serializer()">
+
+=item * L<current_cxn_num()|Search::Elasticsearch::Role::CxnPool/"current_cxn_num()">
+
+=item * L<cxns()|Search::Elasticsearch::Role::CxnPool/"cxns()">
+
+=item * L<seed_nodes()|Search::Elasticsearch::Role::CxnPool/"seed_nodes()">
+
+=item * L<next_cxn_num()|Search::Elasticsearch::Role::CxnPool/"next_cxn_num()">
+
+=item * L<set_cxns()|Search::Elasticsearch::Role::CxnPool/"set_cxns()">
+
+=item * L<request_ok()|Search::Elasticsearch::Role::CxnPool/"request_ok()">
+
+=item * L<request_failed()|Search::Elasticsearch::Role::CxnPool/"request_failed()">
+
+=item * L<should_retry()|Search::Elasticsearch::Role::CxnPool/"should_retry()">
+
+=item * L<should_mark_dead()|Search::Elasticsearch::Role::CxnPool/"should_mark_dead()">
+
+=item * L<cxns_str()|Search::Elasticsearch::Role::CxnPool/"cxns_str()">
+
+=item * L<cxns_seeds_str()|Search::Elasticsearch::Role::CxnPool/"cxns_seeds_str()">
+
+=item * L<retries()|Search::Elasticsearch::Role::CxnPool/"retries()">
+
+=item * L<reset_retries()|Search::Elasticsearch::Role::CxnPool/"reset_retries()">
+
+=back
diff --git a/lib/Search/Elasticsearch/CxnPool/Async/Static/NoPing.pm b/lib/Search/Elasticsearch/CxnPool/Async/Static/NoPing.pm
new file mode 100644
index 0000000..c1f5ef2
--- /dev/null
+++ b/lib/Search/Elasticsearch/CxnPool/Async/Static/NoPing.pm
@@ -0,0 +1,189 @@
+# 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 Search::Elasticsearch::CxnPool::Async::Static::NoPing;
+
+use Moo;
+with 'Search::Elasticsearch::Role::CxnPool::Static::NoPing',
+ 'Search::Elasticsearch::Role::Is_Async';
+
+use Promises qw(deferred);
+use Try::Tiny;
+use namespace::clean;
+
+#===================================
+around 'next_cxn' => sub {
+#===================================
+ my ( $orig, $self ) = @_;
+
+ my $deferred = deferred;
+ try {
+ my $cxn = $orig->($self);
+ $deferred->resolve($cxn);
+ }
+ catch {
+ $deferred->reject($_);
+ };
+
+ $deferred->promise;
+
+};
+
+1;
+
+# ABSTRACT: An async CxnPool for connecting to a remote cluster without the ability to ping.
+
+=head1 SYNOPSIS
+
+ $e = Search::Elasticsearch::Async->new(
+ cxn_pool => 'Async::Static::NoPing'
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ],
+ );
+
+=head1 DESCRIPTION
+
+The L<Async::Static::NoPing|Search::Elasticsearch::CxnPool::Async::Static::NoPing>
+connection pool (like the L<Async::Static|Search::Elasticsearch::CxnPool::Async::Static>
+pool) should be used when your access to the cluster is limited. However, the
+C<Async::Static> pool needs to be able to ping nodes in the cluster, with a
+C<HEAD /> request. If you can't ping your nodes, then you should use the
+C<Async::Static::NoPing> connection pool instead.
+
+Because the cluster cannot be pinged, this CxnPool cannot use a short
+ping request to determine whether nodes are live or not - it just has to
+send requests to the nodes to determine whether they are alive or not.
+
+Most of the time, a dead node will cause the request to fail quickly.
+However, in situations where node failure takes time (eg malfunctioning
+routers or firewalls), a failure may not be reported until the request
+itself times out (see L<Search::Elasticsearch::Cxn/request_timeout>).
+
+Failed nodes will be retried regularly to check if they have recovered.
+
+This class does L<Search::Elasticsearch::Role::CxnPool::Static::NoPing> and
+L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 CONFIGURATION
+
+=head2 C<nodes>
+
+The list of nodes to use to serve requests. Can accept a single node,
+multiple nodes, and defaults to C<localhost:9200> if no C<nodes> are
+specified. See L<Search::Elasticsearch::Role::Cxn/node> for details of the node
+specification.
+
+=head2 See also
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/request_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/dead_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/max_dead_timeout>
+
+=back
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::CxnPool::Static::NoPing>
+
+=over
+
+=item * L<max_retries|Search::Elasticsearch::Role::CxnPool::Static::NoPing/"max_retries">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<randomize_cxns|Search::Elasticsearch::Role::CxnPool/"randomize_cxns">
+
+=back
+
+=head1 METHODS
+
+=head2 C<next_cxn()>
+
+ $cxn_pool->next_cxn->then( sub { my $cxn = shift });
+
+Returns the next available node in round robin fashion - either a live node
+which has previously responded successfully, or a previously failed
+node which should be retried. If all nodes are dead, it will throw
+a C<NoNodes> error.
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::CxnPool::Static::NoPing>
+
+=over
+
+=item * L<should_mark_dead()|Search::Elasticsearch::Role::CxnPool::Static::NoPing/"should_mark_dead()">
+
+=item * L<schedule_check()|Search::Elasticsearch::Role::CxnPool::Static::NoPing/"schedule_check()">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<cxn_factory()|Search::Elasticsearch::Role::CxnPool/"cxn_factory()">
+
+=item * L<logger()|Search::Elasticsearch::Role::CxnPool/"logger()">
+
+=item * L<serializer()|Search::Elasticsearch::Role::CxnPool/"serializer()">
+
+=item * L<current_cxn_num()|Search::Elasticsearch::Role::CxnPool/"current_cxn_num()">
+
+=item * L<cxns()|Search::Elasticsearch::Role::CxnPool/"cxns()">
+
+=item * L<seed_nodes()|Search::Elasticsearch::Role::CxnPool/"seed_nodes()">
+
+=item * L<next_cxn_num()|Search::Elasticsearch::Role::CxnPool/"next_cxn_num()">
+
+=item * L<set_cxns()|Search::Elasticsearch::Role::CxnPool/"set_cxns()">
+
+=item * L<request_ok()|Search::Elasticsearch::Role::CxnPool/"request_ok()">
+
+=item * L<request_failed()|Search::Elasticsearch::Role::CxnPool/"request_failed()">
+
+=item * L<should_retry()|Search::Elasticsearch::Role::CxnPool/"should_retry()">
+
+=item * L<should_mark_dead()|Search::Elasticsearch::Role::CxnPool/"should_mark_dead()">
+
+=item * L<cxns_str()|Search::Elasticsearch::Role::CxnPool/"cxns_str()">
+
+=item * L<cxns_seeds_str()|Search::Elasticsearch::Role::CxnPool/"cxns_seeds_str()">
+
+=item * L<retries()|Search::Elasticsearch::Role::CxnPool/"retries()">
+
+=item * L<reset_retries()|Search::Elasticsearch::Role::CxnPool/"reset_retries()">
+
+=back
+
+
diff --git a/lib/Search/Elasticsearch/CxnPool/Sniff.pm b/lib/Search/Elasticsearch/CxnPool/Sniff.pm
new file mode 100644
index 0000000..d760536
--- /dev/null
+++ b/lib/Search/Elasticsearch/CxnPool/Sniff.pm
@@ -0,0 +1,243 @@
+# 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 Search::Elasticsearch::CxnPool::Sniff;
+
+use Moo;
+with 'Search::Elasticsearch::Role::CxnPool::Sniff',
+ 'Search::Elasticsearch::Role::Is_Sync';
+
+use Search::Elasticsearch::Util qw(throw);
+use namespace::clean;
+
+#===================================
+sub next_cxn {
+#===================================
+ my ($self) = @_;
+
+ $self->sniff if $self->next_sniff <= time();
+
+ my $cxns = $self->cxns;
+ my $total = @$cxns;
+
+ while ( 0 < $total-- ) {
+ my $cxn = $cxns->[ $self->next_cxn_num ];
+ return $cxn if $cxn->is_live;
+ }
+
+ throw( "NoNodes",
+ "No nodes are available: [" . $self->cxns_seeds_str . ']' );
+}
+
+#===================================
+sub sniff {
+#===================================
+ my $self = shift;
+
+ my $cxns = $self->cxns;
+ my $total = @$cxns;
+ my @skipped;
+
+ while ( 0 < $total-- ) {
+ my $cxn = $cxns->[ $self->next_cxn_num ];
+ if ( $cxn->is_dead ) {
+ push @skipped, $cxn;
+ }
+ else {
+ $self->sniff_cxn($cxn) and return;
+ $cxn->mark_dead;
+ }
+ }
+
+ for my $cxn (@skipped) {
+ $self->sniff_cxn($cxn) and return;
+ }
+
+ $self->logger->info("No live nodes available. Trying seed nodes.");
+ for my $seed ( @{ $self->seed_nodes } ) {
+ my $cxn = $self->cxn_factory->new_cxn($seed);
+ $self->sniff_cxn($cxn) and return;
+ }
+
+}
+
+#===================================
+sub sniff_cxn {
+#===================================
+ my ( $self, $cxn ) = @_;
+ return $self->parse_sniff( $cxn->sniff );
+}
+
+1;
+
+# ABSTRACT: A CxnPool for connecting to a local cluster with a dynamic node list
+
+=head1 SYNOPSIS
+
+
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Sniff',
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ],
+ );
+
+=head1 DESCRIPTION
+
+The L<Sniff|Search::Elasticsearch::CxnPool::Sniff> connection pool should be used
+when you B<do> have direct access to the Elasticsearch cluster, eg when
+your web servers and Elasticsearch servers are on the same network.
+The nodes that you specify are used to I<discover> the cluster, which is
+then I<sniffed> to find the current list of live nodes that the cluster
+knows about.
+
+This sniff process is repeated regularly, or whenever a node fails,
+to update the list of healthy nodes. So if you add more nodes to your
+cluster, they will be auto-discovered during a sniff.
+
+If all sniffed nodes fail, then it falls back to sniffing the original
+I<seed> nodes that you specified in C<new()>.
+
+For L<HTTP Cxn classes|Search::Elasticsearch::Role::Cxn>, this module
+will also dynamically detect the C<max_content_length> which the nodes
+in the cluster will accept.
+
+This class does L<Search::Elasticsearch::Role::CxnPool::Sniff> and
+L<Search::Elasticsearch::Role::Is_Sync>.
+
+=head1 CONFIGURATION
+
+=head2 C<nodes>
+
+The list of nodes to use to discover the cluster. Can accept a single node,
+multiple nodes, and defaults to C<localhost:9200> if no C<nodes> are
+specified. See L<Search::Elasticsearch::Role::Cxn/node> for details of the node
+specification.
+
+=head2 See also
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/request_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/sniff_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/sniff_request_timeout>
+
+=back
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::CxnPool::Sniff>
+
+=over
+
+=item * L<sniff_interval|Search::Elasticsearch::Role::CxnPool::Sniff/"sniff_interval">
+
+=item * L<sniff_max_content_length|Search::Elasticsearch::Role::CxnPool::Sniff/"sniff_max_content_length">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<randomize_cxns|Search::Elasticsearch::Role::CxnPool/"randomize_cxns">
+
+=back
+
+=head1 METHODS
+
+=head2 C<next_cxn()>
+
+ $cxn = $cxn_pool->next_cxn
+
+Returns the next available live node (in round robin fashion), or
+throws a C<NoNodes> error if no nodes can be sniffed from the cluster.
+
+=head2 C<schedule_check()>
+
+ $cxn_pool->schedule_check
+
+Forces a sniff before the next Cxn is returned, to updated the list of healthy
+nodes in the cluster.
+
+=head2 C<sniff()>
+
+ $bool = $cxn_pool->sniff
+
+Sniffs the cluster and returns C<true> if the sniff was successful.
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::CxnPool::Sniff>
+
+=over
+
+=item * L<schedule_check()|Search::Elasticsearch::Role::CxnPool::Sniff/"schedule_check()">
+
+=item * L<parse_sniff()|Search::Elasticsearch::Role::CxnPool::Sniff/"parse_sniff()">
+
+=item * L<should_accept_node()|Search::Elasticsearch::Role::CxnPool::Sniff/"should_accept_node()">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<cxn_factory()|Search::Elasticsearch::Role::CxnPool/"cxn_factory()">
+
+=item * L<logger()|Search::Elasticsearch::Role::CxnPool/"logger()">
+
+=item * L<serializer()|Search::Elasticsearch::Role::CxnPool/"serializer()">
+
+=item * L<current_cxn_num()|Search::Elasticsearch::Role::CxnPool/"current_cxn_num()">
+
+=item * L<cxns()|Search::Elasticsearch::Role::CxnPool/"cxns()">
+
+=item * L<seed_nodes()|Search::Elasticsearch::Role::CxnPool/"seed_nodes()">
+
+=item * L<next_cxn_num()|Search::Elasticsearch::Role::CxnPool/"next_cxn_num()">
+
+=item * L<set_cxns()|Search::Elasticsearch::Role::CxnPool/"set_cxns()">
+
+=item * L<request_ok()|Search::Elasticsearch::Role::CxnPool/"request_ok()">
+
+=item * L<request_failed()|Search::Elasticsearch::Role::CxnPool/"request_failed()">
+
+=item * L<should_retry()|Search::Elasticsearch::Role::CxnPool/"should_retry()">
+
+=item * L<should_mark_dead()|Search::Elasticsearch::Role::CxnPool/"should_mark_dead()">
+
+=item * L<cxns_str()|Search::Elasticsearch::Role::CxnPool/"cxns_str()">
+
+=item * L<cxns_seeds_str()|Search::Elasticsearch::Role::CxnPool/"cxns_seeds_str()">
+
+=item * L<retries()|Search::Elasticsearch::Role::CxnPool/"retries()">
+
+=item * L<reset_retries()|Search::Elasticsearch::Role::CxnPool/"reset_retries()">
+
+=back
+
diff --git a/lib/Search/Elasticsearch/CxnPool/Static.pm b/lib/Search/Elasticsearch/CxnPool/Static.pm
new file mode 100644
index 0000000..f4d6feb
--- /dev/null
+++ b/lib/Search/Elasticsearch/CxnPool/Static.pm
@@ -0,0 +1,187 @@
+# 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 Search::Elasticsearch::CxnPool::Static;
+
+use Moo;
+with 'Search::Elasticsearch::Role::CxnPool::Static',
+ 'Search::Elasticsearch::Role::Is_Sync';
+use Search::Elasticsearch::Util qw(throw);
+use namespace::clean;
+
+#===================================
+sub next_cxn {
+#===================================
+ my ($self) = @_;
+
+ my $cxns = $self->cxns;
+ my $total = @$cxns;
+
+ my $now = time();
+ my @skipped;
+
+ while ( $total-- ) {
+ my $cxn = $cxns->[ $self->next_cxn_num ];
+ return $cxn if $cxn->is_live;
+
+ if ( $cxn->next_ping < $now ) {
+ return $cxn if $cxn->pings_ok;
+ }
+ else {
+ push @skipped, $cxn;
+ }
+ }
+
+ for my $cxn (@skipped) {
+ return $cxn if $cxn->pings_ok;
+ }
+
+ $_->force_ping for @$cxns;
+
+ throw( "NoNodes", "No nodes are available: [" . $self->cxns_str . ']' );
+}
+
+1;
+
+__END__
+
+# ABSTRACT: A CxnPool for connecting to a remote cluster with a static list of nodes.
+
+=head1 SYNOPSIS
+
+
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Static' # default
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ],
+ );
+
+=head1 DESCRIPTION
+
+The L<Static|Search::Elasticsearch::CxnPool::Static> connection pool, which is the
+default, should be used when you don't have direct access to the Elasticsearch
+cluster, eg when you are accessing the cluster through a proxy. It
+round-robins through the nodes that you specified, and pings each node
+before it is used for the first time, to ensure that it is responding.
+
+If any node fails, then all nodes are pinged before the next request to
+ensure that they are still alive and responding. Failed nodes will be
+pinged regularly to check if they have recovered.
+
+This class does L<Search::Elasticsearch::Role::CxnPool::Static> and
+L<Search::Elasticsearch::Role::Is_Sync>.
+
+=head1 CONFIGURATION
+
+=head2 C<nodes>
+
+The list of nodes to use to serve requests. Can accept a single node,
+multiple nodes, and defaults to C<localhost:9200> if no C<nodes> are
+specified. See L<Search::Elasticsearch::Role::Cxn/node> for details of the node
+specification.
+
+=head2 See also
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/request_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/ping_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/dead_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/max_dead_timeout>
+
+=back
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<randomize_cxns|Search::Elasticsearch::Role::CxnPool/"randomize_cxns">
+
+=back
+
+
+=head1 METHODS
+
+=head2 C<next_cxn()>
+
+ $cxn = $cxn_pool->next_cxn
+
+Returns the next available live node (in round robin fashion), or
+throws a C<NoNodes> error if no nodes respond to ping requests.
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::CxnPool::Static>
+
+=over
+
+=item * L<schedule_check()|Search::Elasticsearch::Role::CxnPool::Static/"schedule_check()">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<cxn_factory()|Search::Elasticsearch::Role::CxnPool/"cxn_factory()">
+
+=item * L<logger()|Search::Elasticsearch::Role::CxnPool/"logger()">
+
+=item * L<serializer()|Search::Elasticsearch::Role::CxnPool/"serializer()">
+
+=item * L<current_cxn_num()|Search::Elasticsearch::Role::CxnPool/"current_cxn_num()">
+
+=item * L<cxns()|Search::Elasticsearch::Role::CxnPool/"cxns()">
+
+=item * L<seed_nodes()|Search::Elasticsearch::Role::CxnPool/"seed_nodes()">
+
+=item * L<next_cxn_num()|Search::Elasticsearch::Role::CxnPool/"next_cxn_num()">
+
+=item * L<set_cxns()|Search::Elasticsearch::Role::CxnPool/"set_cxns()">
+
+=item * L<request_ok()|Search::Elasticsearch::Role::CxnPool/"request_ok()">
+
+=item * L<request_failed()|Search::Elasticsearch::Role::CxnPool/"request_failed()">
+
+=item * L<should_retry()|Search::Elasticsearch::Role::CxnPool/"should_retry()">
+
+=item * L<should_mark_dead()|Search::Elasticsearch::Role::CxnPool/"should_mark_dead()">
+
+=item * L<cxns_str()|Search::Elasticsearch::Role::CxnPool/"cxns_str()">
+
+=item * L<cxns_seeds_str()|Search::Elasticsearch::Role::CxnPool/"cxns_seeds_str()">
+
+=item * L<retries()|Search::Elasticsearch::Role::CxnPool/"retries()">
+
+=item * L<reset_retries()|Search::Elasticsearch::Role::CxnPool/"reset_retries()">
+
+=back
diff --git a/lib/Search/Elasticsearch/CxnPool/Static/NoPing.pm b/lib/Search/Elasticsearch/CxnPool/Static/NoPing.pm
new file mode 100644
index 0000000..b854415
--- /dev/null
+++ b/lib/Search/Elasticsearch/CxnPool/Static/NoPing.pm
@@ -0,0 +1,171 @@
+# 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 Search::Elasticsearch::CxnPool::Static::NoPing;
+
+use Moo;
+with 'Search::Elasticsearch::Role::CxnPool::Static::NoPing',
+ 'Search::Elasticsearch::Role::Is_Sync';
+use Search::Elasticsearch::Util qw(throw);
+use namespace::clean;
+
+1;
+
+__END__
+
+# ABSTRACT: A CxnPool for connecting to a remote cluster without the ability to ping.
+
+=head1 SYNOPSIS
+
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Static::NoPing'
+ nodes => [
+ 'search1:9200',
+ 'search2:9200'
+ ],
+ );
+
+=head1 DESCRIPTION
+
+The L<Static::NoPing|Search::Elasticsearch::CxnPool::Static::NoPing> connection
+pool (like the L<Static|Search::Elasticsearch::CxnPool::Static> pool) should be used
+when your access to the cluster is limited. However, the C<Static> pool needs
+to be able to ping nodes in the cluster, with a C<HEAD /> request. If you
+can't ping your nodes, then you should use the C<Static::NoPing>
+connection pool instead.
+
+Because the cluster cannot be pinged, this CxnPool cannot use a short
+ping request to determine whether nodes are live or not - it just has to
+send requests to the nodes to determine whether they are alive or not.
+
+Most of the time, a dead node will cause the request to fail quickly.
+However, in situations where node failure takes time (eg malfunctioning
+routers or firewalls), a failure may not be reported until the request
+itself times out (see L<Search::Elasticsearch::Cxn/request_timeout>).
+
+Failed nodes will be retried regularly to check if they have recovered.
+
+This class does L<Search::Elasticsearch::Role::CxnPool::Static::NoPing> and
+L<Search::Elasticsearch::Role::Is_Sync>.
+
+=head1 CONFIGURATION
+
+=head2 C<nodes>
+
+The list of nodes to use to serve requests. Can accept a single node,
+multiple nodes, and defaults to C<localhost:9200> if no C<nodes> are
+specified. See L<Search::Elasticsearch::Role::Cxn/node> for details of the node
+specification.
+
+=head2 See also
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/request_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/dead_timeout>
+
+=item *
+
+L<Search::Elasticsearch::Role::Cxn/max_dead_timeout>
+
+=back
+
+=head2 Inherited configuration
+
+From L<Search::Elasticsearch::Role::CxnPool::Static::NoPing>
+
+=over
+
+=item * L<max_retries|Search::Elasticsearch::Role::CxnPool::Static::NoPing/"max_retries">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<randomize_cxns|Search::Elasticsearch::Role::CxnPool/"randomize_cxns">
+
+=back
+
+=head1 METHODS
+
+=head2 C<next_cxn()>
+
+ $cxn = $cxn_pool->next_cxn
+
+Returns the next available node in round robin fashion - either a live node
+which has previously responded successfully, or a previously failed
+node which should be retried. If all nodes are dead, it will throw
+a C<NoNodes> error.
+
+=head2 Inherited methods
+
+From L<Search::Elasticsearch::Role::CxnPool::Static::NoPing>
+
+=over
+
+=item * L<should_mark_dead()|Search::Elasticsearch::Role::CxnPool::Static::NoPing/"should_mark_dead()">
+
+=item * L<schedule_check()|Search::Elasticsearch::Role::CxnPool::Static::NoPing/"schedule_check()">
+
+=back
+
+From L<Search::Elasticsearch::Role::CxnPool>
+
+=over
+
+=item * L<cxn_factory()|Search::Elasticsearch::Role::CxnPool/"cxn_factory()">
+
+=item * L<logger()|Search::Elasticsearch::Role::CxnPool/"logger()">
+
+=item * L<serializer()|Search::Elasticsearch::Role::CxnPool/"serializer()">
+
+=item * L<current_cxn_num()|Search::Elasticsearch::Role::CxnPool/"current_cxn_num()">
+
+=item * L<cxns()|Search::Elasticsearch::Role::CxnPool/"cxns()">
+
+=item * L<seed_nodes()|Search::Elasticsearch::Role::CxnPool/"seed_nodes()">
+
+=item * L<next_cxn_num()|Search::Elasticsearch::Role::CxnPool/"next_cxn_num()">
+
+=item * L<set_cxns()|Search::Elasticsearch::Role::CxnPool/"set_cxns()">
+
+=item * L<request_ok()|Search::Elasticsearch::Role::CxnPool/"request_ok()">
+
+=item * L<request_failed()|Search::Elasticsearch::Role::CxnPool/"request_failed()">
+
+=item * L<should_retry()|Search::Elasticsearch::Role::CxnPool/"should_retry()">
+
+=item * L<should_mark_dead()|Search::Elasticsearch::Role::CxnPool/"should_mark_dead()">
+
+=item * L<cxns_str()|Search::Elasticsearch::Role::CxnPool/"cxns_str()">
+
+=item * L<cxns_seeds_str()|Search::Elasticsearch::Role::CxnPool/"cxns_seeds_str()">
+
+=item * L<retries()|Search::Elasticsearch::Role::CxnPool/"retries()">
+
+=item * L<reset_retries()|Search::Elasticsearch::Role::CxnPool/"reset_retries()">
+
+=back
+
+
diff --git a/lib/Search/Elasticsearch/Error.pm b/lib/Search/Elasticsearch/Error.pm
new file mode 100644
index 0000000..885da1d
--- /dev/null
+++ b/lib/Search/Elasticsearch/Error.pm
@@ -0,0 +1,315 @@
+# 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 Search::Elasticsearch::Error;
+
+our $DEBUG = 0;
+
+@Search::Elasticsearch::Error::Internal::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::Param::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::NoNodes::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::ProductCheck::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::Unauthorized::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::Forbidden::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::Illegal::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::Request::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::Timeout::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::Cxn::ISA = __PACKAGE__;
+@Search::Elasticsearch::Error::Serializer::ISA = __PACKAGE__;
+
+@Search::Elasticsearch::Error::Conflict::ISA
+ = ( 'Search::Elasticsearch::Error::Request', __PACKAGE__ );
+
+@Search::Elasticsearch::Error::Missing::ISA
+ = ( 'Search::Elasticsearch::Error::Request', __PACKAGE__ );
+
+@Search::Elasticsearch::Error::RequestTimeout::ISA
+ = ( 'Search::Elasticsearch::Error::Request', __PACKAGE__ );
+
+@Search::Elasticsearch::Error::ContentLength::ISA
+ = ( __PACKAGE__, 'Search::Elasticsearch::Error::Request' );
+
+@Search::Elasticsearch::Error::SSL::ISA
+ = ( __PACKAGE__, 'Search::Elasticsearch::Error::Cxn' );
+
+@Search::Elasticsearch::Error::BadGateway::ISA
+ = ( 'Search::Elasticsearch::Error::Cxn', __PACKAGE__ );
+
+@Search::Elasticsearch::Error::Unavailable::ISA
+ = ( 'Search::Elasticsearch::Error::Cxn', __PACKAGE__ );
+
+@Search::Elasticsearch::Error::GatewayTimeout::ISA
+ = ( 'Search::Elasticsearch::Error::Cxn', __PACKAGE__ );
+
+use overload (
+ '""' => '_stringify',
+ 'cmp' => '_compare',
+);
+
+use Data::Dumper();
+
+#===================================
+sub new {
+#===================================
+ my ( $class, $type, $msg, $vars, $caller ) = @_;
+ return $type if ref $type;
+ $caller ||= 0;
+
+ my $error_class = 'Search::Elasticsearch::Error::' . $type;
+ $msg = 'Unknown error' unless defined $msg;
+
+ local $DEBUG = 2 if $type eq 'Internal';
+
+ my $stack = $class->_stack;
+
+ my $self = bless {
+ type => $type,
+ text => $msg,
+ vars => $vars,
+ stack => $stack,
+ }, $error_class;
+
+ return $self;
+}
+
+#===================================
+sub is {
+#===================================
+ my $self = shift;
+ for (@_) {
+ return 1 if $self->isa("Search::Elasticsearch::Error::$_");
+ }
+ return 0;
+}
+
+#===================================
+sub _stringify {
+#===================================
+ my $self = shift;
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Indent = !!$DEBUG;
+
+ unless ( $self->{msg} ) {
+ my $stack = $self->{stack};
+ my $caller = $stack->[0];
+ $self->{msg} = sprintf( "[%s] ** %s, called from sub %s at %s line %d.",
+ $self->{type}, $self->{text}, @{$caller}[ 3, 1, 2 ] );
+
+ if ( $self->{vars} ) {
+ $self->{msg} .= sprintf( " With vars: %s\n",
+ Data::Dumper::Dumper $self->{vars} );
+ }
+
+ if ( @$stack > 1 ) {
+ $self->{msg}
+ .= sprintf( "Stacktrace:\n%s\n", $self->stacktrace($stack) );
+ }
+ }
+ return $self->{msg};
+
+}
+
+#===================================
+sub _compare {
+#===================================
+ my ( $self, $other, $swap ) = @_;
+ $self .= '';
+ ( $self, $other ) = ( $other, $self ) if $swap;
+ return $self cmp $other;
+}
+
+#===================================
+sub _stack {
+#===================================
+ my $self = shift;
+ my $caller = shift() || 2;
+
+ my @stack;
+ while ( my @caller = caller( ++$caller ) ) {
+ next if $caller[0] eq 'Try::Tiny';
+
+ if ( $caller[3] =~ /^(.+)::__ANON__\[(.+):(\d+)\]$/ ) {
+ @caller = ( $1, $2, $3, '(ANON)' );
+ }
+ elsif ( $caller[1] =~ /^\(eval \d+\)/ ) {
+ $caller[3] = "modified(" . $caller[3] . ")";
+ }
+
+ next
+ if $caller[0] =~ /^Search::Elasticsearch/
+ and ( $DEBUG < 2 or $caller[3] eq 'Try::Tiny::try' );
+ push @stack, [ @caller[ 0, 1, 2, 3 ] ];
+ last unless $DEBUG > 1;
+ }
+ return \@stack;
+}
+
+#===================================
+sub stacktrace {
+#===================================
+ my $self = shift;
+ my $stack = shift || $self->_stack();
+
+ my $o = sprintf "%s\n%-4s %-50s %-5s %s\n%s\n",
+ '-' x 80, '#', 'Package', 'Line', 'Sub-routine', '-' x 80;
+
+ my $i = 1;
+ for (@$stack) {
+ $o .= sprintf "%-4d %-50s %4d %s\n", $i++, @{$_}[ 0, 2, 3 ];
+ }
+
+ return $o .= ( '-' x 80 ) . "\n";
+}
+
+#===================================
+sub TO_JSON {
+#===================================
+ my $self = shift;
+ return $self->_stringify;
+}
+1;
+
+# ABSTRACT: Errors thrown by Search::Elasticsearch
+
+=head1 DESCRIPTION
+
+Errors thrown by Search::Elasticsearch are error objects, which can include
+a stack trace and information to help debug problems. An error object
+consists of the following:
+
+ {
+ type => $type, # eg Missing
+ text => 'Error message',
+ vars => {...}, # vars which may help to explain the error
+ stack => [...], # a stack trace
+ }
+
+The C<$Search::Elasticsearch::Error::DEBUG> variable can be set to C<1> or C<2>
+to increase the verbosity of errors.
+
+Error objects stringify to a human readable error message when used in text
+context (for example: C<print 'Oh no! '.$error>). They also support the C<TO_JSON>
+method to support conversion to JSON when L<JSON/convert_blessed> is enabled.
+
+=head1 ERROR CLASSES
+
+The following error classes are defined:
+
+=over
+
+=item * C<Search::Elasticsearch::Error::Param>
+
+A bad parameter has been passed to a method.
+
+=item * C<Search::Elasticsearch::Error::Request>
+
+There was some generic error performing your request in Elasticsearch.
+This error is triggered by HTTP status codes C<400> and C<500>. This class
+has the following sub-classes:
+
+=over
+
+=item * C<Search::Elasticsearch::Error::Unauthorized>
+
+Invalid (or no) username/password provided as C<userinfo> for a password
+protected service. These errors are triggered by the C<401> HTTP status code.
+
+=item * C<Search::Elasticsearch::Error::Missing>
+
+A resource that you requested was not found. These errors are triggered
+by the C<404> HTTP status code.
+
+=item * C<Elastisearch::Error::Conflict>
+
+Your request could not be performed because of some conflict. For instance,
+if you try to delete a document with a particular version number, and the
+document has already changed, it will throw a C<Conflict> error. If it can,
+it will include the C<current_version> in the error vars. This error
+is triggered by the C<409> HTTP status code.
+
+=item * C<Search::Elasticsearch::Error::ContentLength>
+
+The request body was longer than the
+L<max_content_length|Search::Elasticsearch::Role::Cxn/max_content_length>.
+
+=item * C<Search::Elasticsearch::Error::RequestTimeout>
+
+The request took longer than the specified C<timeout>. Currently only
+applies to the
+L<cluster_health|Search::Elasticsearch::Client::6_0::Direct::Cluster/cluster_health()>
+request.
+
+=back
+
+=item * C<Search::Elasticsearch::Error::Timeout>
+
+The request timed out.
+
+=item * C<Search::Elasticsearch::Error::Cxn>
+
+There was an error connecting to a node in the cluster. This error
+indicates node failure and will be retried on another node.
+This error has the following sub-classes:
+
+=over
+
+=item * C<Search::Elasticsearch::Error::Unavailable>
+
+The current node is unable to handle your request at the moment. Your
+request will be retried on another node. This error is triggered by
+the C<503> HTTP status code.
+
+=item * C<Search::Elasticsearch::Error::BadGateway>
+
+A proxy between the client and Elasticsearch is unable to connect to Elasticsearch.
+This error is triggered by the C<502> HTTP status code.
+
+=item * C<Search::Elasticsearch::Error::GatewayTimeout>
+
+A proxy between the client and Elasticsearch is unable to connect to Elasticsearch
+within its own timeout. This error is triggered by the C<504> HTTP status code.
+
+=item * C<Search::Elasticsearch::Error::SSL>
+
+There was a problem validating the SSL certificate. Not all
+backends support this error type.
+
+=back
+
+=item * C<Search::Elasticsearch::Error::Forbidden>
+
+Either the cluster was unable to process the request because it is currently
+blocking, eg there are not enough master nodes to form a cluster, or
+because the authenticated user is trying to perform an unauthorized
+action. This error is triggered by the C<403> HTTP status code.
+
+=item * C<Search::Elasticsearch::Error::Illegal>
+
+You have attempted to perform an illegal operation.
+For instance, you attempted to use a Scroll helper in a different process
+after forking.
+
+=item * C<Search::Elasticsearch::Error::Serializer>
+
+There was an error serializing a variable or deserializing a string.
+
+=item * C<Elasticsarch::Error::Internal>
+
+An internal error occurred - please report this as a bug in
+this module.
+
+=back
diff --git a/lib/Search/Elasticsearch/Logger/LogAny.pm b/lib/Search/Elasticsearch/Logger/LogAny.pm
new file mode 100644
index 0000000..f9b6aeb
--- /dev/null
+++ b/lib/Search/Elasticsearch/Logger/LogAny.pm
@@ -0,0 +1,137 @@
+# 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 Search::Elasticsearch::Logger::LogAny;
+
+use Moo;
+with 'Search::Elasticsearch::Role::Logger';
+use Search::Elasticsearch::Util qw(parse_params to_list);
+use namespace::clean;
+
+use Log::Any 1.02 ();
+use Log::Any::Adapter();
+
+#===================================
+sub _build_log_handle {
+#===================================
+ my $self = shift;
+ if ( my @args = to_list( $self->log_to ) ) {
+ Log::Any::Adapter->set( { category => $self->log_as }, @args );
+ }
+ Log::Any->get_logger( category => $self->log_as );
+}
+
+#===================================
+sub _build_trace_handle {
+#===================================
+ my $self = shift;
+ if ( my @args = to_list( $self->trace_to ) ) {
+ Log::Any::Adapter->set( { category => $self->trace_as }, @args );
+ }
+ Log::Any->get_logger( category => $self->trace_as );
+}
+
+#===================================
+sub _build_deprecate_handle {
+#===================================
+ my $self = shift;
+ if ( my @args = to_list( $self->deprecate_to ) ) {
+ Log::Any::Adapter->set( { category => $self->deprecate_as }, @args );
+ }
+ Log::Any->get_logger(
+ default_adapter => 'Stderr',
+ category => $self->deprecate_as
+ );
+}
+
+1;
+
+# ABSTRACT: A Log::Any-based Logger implementation
+
+=head1 DESCRIPTION
+
+L<Search::Elasticsearch::Logger::LogAny> provides event logging and the tracing
+of request/response conversations with Elasticsearch nodes via the
+L<Log::Any> module.
+
+I<Logging> refers to log events, such as node failures, pings, sniffs, etc,
+and should be enabled for monitoring purposes.
+
+I<Tracing> refers to the actual HTTP requests and responses sent
+to Elasticsearch nodes. Tracing can be enabled for debugging purposes,
+or for generating a pretty-printed C<curl> script which can be used for
+reporting problems.
+
+I<Deprecations> refers to deprecation warnings returned by Elasticsearch
+5.x and above. Deprecations are logged to STDERR by default.
+
+=head1 CONFIGURATION
+
+Logging and tracing can be enabled using L<Log::Any::Adapter>, or by
+passing options to L<Search::Elasticsearch/new()>.
+
+=head2 USING LOG::ANY::ADAPTER
+
+Send all logging and tracing to C<STDERR>:
+
+ use Log::Any::Adapter qw(Stderr);
+ use Search::Elasticsearch;
+ my $e = Search::Elasticsearch->new;
+
+Send logging and deprecations to a file, and tracing to Stderr:
+
+ use Log::Any::Adapter();
+ Log::Any::Adapter->set(
+ { category => 'elasticsearch.event' },
+ 'File',
+ '/path/to/file.log'
+ );
+ Log::Any::Adapter->set(
+ { category => 'elasticsearch.trace' },
+ 'Stderr'
+ );
+ Log::Any::Adapter->set(
+ { category => 'elasticsearch.deprecation' },
+ 'File',
+ '/path/to/deprecations.log'
+ );
+
+ use Search::Elasticsearch;
+ my $e = Search::Elasticsearch->new;
+
+=head2 USING C<log_to>, C<trace_to> AND C<deprecate_to>
+
+Send all logging and tracing to C<STDERR>:
+
+ use Search::Elasticsearch;
+ my $e = Search::Elasticsearch->new(
+ log_to => 'Stderr',
+ trace_to => 'Stderr',
+ deprecate_to => 'Stderr' # default
+ );
+
+Send logging and deprecations to a file, and tracing to Stderr:
+
+ use Search::Elasticsearch;
+ my $e = Search::Elasticsearch->new(
+ log_to => ['File', '/path/to/file.log'],
+ trace_to => 'Stderr',
+ deprecate_to => ['File', '/path/to/deprecations.log'],
+ );
+
+See L<Log::Any::Adapter> for more.
+
diff --git a/lib/Search/Elasticsearch/Role/API.pm b/lib/Search/Elasticsearch/Role/API.pm
new file mode 100644
index 0000000..d659de1
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/API.pm
@@ -0,0 +1,124 @@
+# 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 Search::Elasticsearch::Role::API;
+
+use Moo::Role;
+requires 'api_version';
+requires 'api';
+
+use Scalar::Util qw(looks_like_number);
+use Search::Elasticsearch::Util qw(throw);
+use namespace::clean;
+
+our %Handler = (
+ string => \&_string,
+ time => \&_string,
+ date => \&_string,
+ list => \&_list,
+ boolean => \&_bool,
+ enum => \&_list,
+ number => \&_num,
+ int => \&_num,
+ integer => \&_num,
+ float => \&_num,
+ double => \&_num,
+ "number|string" => \&_numOrString,
+ "boolean|long" => \&_booleanOrLong
+);
+
+#===================================
+sub _bool {
+#===================================
+ my $val = _detect_bool(@_);
+ return ( $val && $val ne 'false' ) ? 'true' : 'false';
+}
+
+#===================================
+sub _detect_bool {
+#===================================
+ my $val = shift;
+ return '' unless defined $val;
+ if ( ref $val eq 'SCALAR' ) {
+ return 'false' if $$val eq 0;
+ return 'true' if $$val eq 1;
+ }
+ elsif ( UNIVERSAL::isa( $val, "JSON::PP::Boolean" ) ) {
+ return "$val" ? 'true' : 'false';
+ }
+ return "$val";
+}
+
+#===================================
+sub _list {
+#===================================
+ return join ",", map { _detect_bool($_) } #
+ ref $_[0] eq 'ARRAY' ? @{ $_[0] } : $_[0];
+}
+
+#===================================
+sub _num {
+#===================================
+ return 0 + $_[0];
+}
+
+#===================================
+sub _string {
+#===================================
+ return "$_[0]";
+}
+
+#===================================
+sub _numOrString {
+#===================================
+ if (looks_like_number($_[0])) {
+ return _num($_[0]);
+ }
+ return _string($_[0]);
+}
+
+#===================================
+sub _booleanOrLong {
+#===================================
+ if (looks_like_number($_[0])) {
+ return _num($_[0]);
+ }
+ my $val = _detect_bool(@_);
+ return ( $val && $val ne 'false' ) ? 'true' : 'false';
+}
+
+#===================================
+sub _qs_init {
+#===================================
+ my $class = shift;
+ my $API = shift;
+ for my $spec ( keys %$API ) {
+ my $qs = $API->{$spec}{qs};
+ for my $param ( keys %$qs ) {
+ my $handler = $Handler{ $qs->{$param} }
+ or throw( "Internal",
+ "Unknown type <"
+ . $qs->{$param}
+ . "> for param <$param> in API <$spec>" );
+ $qs->{$param} = $handler;
+ }
+ }
+}
+
+1;
+
+# ABSTRACT: Provides common functionality for API implementations
diff --git a/lib/Search/Elasticsearch/Role/Client.pm b/lib/Search/Elasticsearch/Role/Client.pm
new file mode 100644
index 0000000..4eb7b68
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Client.pm
@@ -0,0 +1,41 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Role::Client;
+
+use Moo::Role;
+use namespace::clean;
+
+requires 'parse_request';
+
+has 'transport' => ( is => 'ro', required => 1 );
+has 'logger' => ( is => 'ro', required => 1 );
+
+#===================================
+sub perform_request {
+#===================================
+ my $self = shift;
+ my $request = $self->parse_request(@_);
+ return $self->transport->perform_request($request);
+}
+
+1;
+
+__END__
+
+# ABSTRACT: Provides common functionality for Client implementations
+
+=head1 DESCRIPTION
+
+This role provides a common C<perform_request()> method for Client
+implementations.
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+This method takes whatever arguments it is passed and passes them directly to
+a C<parse_request()> method (which should be provided by Client implementations).
+The C<parse_request()> method should return a request suitable for passing
+to L<Search::Elasticsearch::Transport/perform_request()>.
diff --git a/lib/Search/Elasticsearch/Role/Client/Direct.pm b/lib/Search/Elasticsearch/Role/Client/Direct.pm
new file mode 100644
index 0000000..141e5c6
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Client/Direct.pm
@@ -0,0 +1,212 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Role::Client::Direct;
+
+use Moo::Role;
+with 'Search::Elasticsearch::Role::Client';
+use Search::Elasticsearch::Util qw(load_plugin is_compat throw);
+
+use Try::Tiny;
+use Package::Stash 0.34 ();
+use Any::URI::Escape qw(uri_escape);
+use namespace::clean;
+
+#===================================
+sub parse_request {
+#===================================
+ my $self = shift;
+ my $defn = shift || {};
+ my $params = { ref $_[0] ? %{ shift() } : @_ };
+
+ my $request;
+ try {
+ $request = {
+ ignore => delete $params->{ignore} || [],
+ method => $defn->{method} || 'GET',
+ serialize => $defn->{serialize} || 'std',
+ path => $self->_parse_path( $defn, $params ),
+ body => $self->_parse_body( $defn->{body}, $params ),
+ qs => $self->_parse_qs( $defn->{qs}, $params ),
+ };
+ }
+ catch {
+ chomp $_;
+ my $name = $defn->{name} || '<unknown method>';
+ $self->logger->throw_error( 'Param', "$_ in ($name) request. " );
+ };
+ return $request;
+}
+
+#===================================
+sub _parse_path {
+#===================================
+ my ( $self, $defn, $params ) = @_;
+ return delete $params->{path}
+ if $params->{path};
+ my $paths = $defn->{paths};
+ my $parts = $defn->{parts};
+
+ my %args;
+ keys %$parts;
+ no warnings 'uninitialized';
+ while ( my ( $key, $req ) = each %$parts ) {
+ my $val = delete $params->{$key};
+ if ( ref $val eq 'ARRAY' ) {
+ die "Param ($key) must contain a single value\n"
+ if @$val > 1 and not $req->{multi};
+ $val = join ",", @$val;
+ }
+ if ( !length $val ) {
+ die "Missing required param ($key)\n"
+ if $req->{required};
+ next;
+ }
+ utf8::encode($val);
+ $args{$key} = uri_escape($val);
+ }
+PATH: for my $path (@$paths) {
+ my @keys = keys %{ $path->[0] };
+ next PATH unless @keys == keys %args;
+ for (@keys) {
+ next PATH unless exists $args{$_};
+ }
+ my ( $pos, @parts ) = @$path;
+ for ( keys %$pos ) {
+ $parts[ $pos->{$_} ] = $args{$_};
+ }
+ return join "/", '', @parts;
+ }
+
+ throw(
+ 'Internal',
+ "Couldn't determine path",
+ { params => $params, defn => $defn }
+ );
+}
+
+#===================================
+sub _parse_body {
+#===================================
+ my ( $self, $defn, $params ) = @_;
+ if ( defined $defn ) {
+ die("Missing required param (body)\n")
+ if $defn->{required} && !$params->{body};
+ return delete $params->{body};
+ }
+ die("Unknown param (body)\n") if $params->{body};
+ return undef;
+}
+
+#===================================
+sub _parse_qs {
+#===================================
+ my ( $self, $handlers, $params ) = @_;
+ die "No (qs) defined\n" unless $handlers;
+ my %qs;
+
+ if ( my $raw = delete $params->{params} ) {
+ die("Arg (params) shoud be a hashref\n")
+ unless ref $raw eq 'HASH';
+ %qs = %$raw;
+ }
+
+ for my $key ( keys %$params ) {
+ my $handler = $handlers->{$key}
+ or die("Unknown param ($key)\n");
+ $qs{$key} = $handler->( delete $params->{$key} );
+ }
+ return \%qs;
+}
+
+#===================================
+sub _install_api {
+#===================================
+ my ( $class, $group ) = @_;
+ my $defns = $class->api;
+ my $stash = Package::Stash->new($class);
+
+ my $group_qr = $group ? qr/$group\./ : qr//;
+ for my $action ( keys %$defns ) {
+ my ($name) = ( $action =~ /^$group_qr([^.]+)$/ )
+ or next;
+ next if $stash->has_symbol( '&' . $name );
+
+ my %defn = ( name => $name, %{ $defns->{$action} } );
+ $stash->add_symbol(
+ '&' . $name => sub {
+ shift->perform_request( \%defn, @_ );
+ }
+ );
+ }
+}
+
+#===================================
+sub _build_namespace {
+#===================================
+ my ( $self, $ns ) = @_;
+ my $class = load_plugin( $self->_namespace, [$ns] );
+ return $class->new(
+ { transport => $self->transport,
+ logger => $self->logger
+ }
+ );
+}
+
+#===================================
+sub _build_helper {
+#===================================
+ my ( $self, $name, $sub_class ) = @_;
+ my $class = load_plugin( 'Search::Elasticsearch', $sub_class );
+ is_compat( $name . '_helper_class', $self->transport, $class );
+ return $class;
+}
+
+1;
+
+# ABSTRACT: Request parsing for Direct clients
+
+=head1 DESCRIPTION
+
+This role provides the single C<parse_request()> method for classes
+which need to parse an API definition from L<Search::Elasticsearch::Role::API>
+and convert it into a request which can be passed to
+L<Search::Elasticsearch::Transport/perform_request()>.
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+ $request = $client->parse_request(\%defn,\%params);
+
+The C<%defn> is a definition returned by L<Search::Elasticsearch::Role::API/api()>
+with an extra key C<name> which should be the name of the method that
+was called on the client. For instance if the user calls C<< $client->search >>,
+then the C<name> should be C<"search">.
+
+C<parse_request()> will turn the parameters that have been passed in into
+a C<path> (via L<Search::Elasticsearch::Util::API::Path/path_init()>), a query-string
+hash (via L<Search::Elasticsearch::Util::API::QS/qs_init>) and will through a
+C<body> value directly.
+
+B<NOTE:> If a C<path> key is specified in the C<%params> then it will be used
+directly, instead of trying to build path from the path template. Similarly,
+if a C<params> key is specified in the C<%params>, then it will be used
+as a basis for the query string hash. For instance:
+
+ $client->perform_request(
+ {
+ method => 'GET',
+ name => 'new_method'
+ },
+ {
+ path => '/new/method',
+ params => { foo => 'bar' },
+ body => \%body
+ }
+ );
+
+This makes it easy to add support for custom plugins or new functionality
+not yet supported by the released client.
+
diff --git a/lib/Search/Elasticsearch/Role/Cxn.pm b/lib/Search/Elasticsearch/Role/Cxn.pm
new file mode 100644
index 0000000..706b9de
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Cxn.pm
@@ -0,0 +1,842 @@
+# 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 Search::Elasticsearch::Role::Cxn;
+
+our $PRODUCT_CHECK_HEADER = 'x-elastic-product';
+our $PRODUCT_CHECK_VALUE = 'Elasticsearch';
+
+use Moo::Role;
+use Search::Elasticsearch::Util qw(parse_params throw to_list);
+use List::Util qw(min);
+use Try::Tiny;
+use URI();
+use IO::Compress::Deflate();
+use IO::Uncompress::Inflate();
+use IO::Compress::Gzip();
+use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
+use Search::Elasticsearch::Util qw(to_list);
+use namespace::clean;
+use Net::IP;
+
+requires qw(perform_request error_from_text handle);
+
+has 'host' => ( is => 'ro', required => 1 );
+has 'port' => ( is => 'ro', required => 1 );
+has 'uri' => ( is => 'ro', required => 1 );
+has 'request_timeout' => ( is => 'ro', default => 30 );
+has 'ping_timeout' => ( is => 'ro', default => 2 );
+has 'sniff_timeout' => ( is => 'ro', default => 1 );
+has 'sniff_request_timeout' => ( is => 'ro', default => 2 );
+has 'next_ping' => ( is => 'rw', default => 0 );
+has 'ping_failures' => ( is => 'rw', default => 0 );
+has 'dead_timeout' => ( is => 'ro', default => 60 );
+has 'max_dead_timeout' => ( is => 'ro', default => 3600 );
+has 'serializer' => ( is => 'ro', required => 1 );
+has 'logger' => ( is => 'ro', required => 1 );
+has 'handle_args' => ( is => 'ro', default => sub { {} } );
+has 'default_qs_params' => ( is => 'ro', default => sub { {} } );
+has 'scheme' => ( is => 'ro' );
+has 'is_https' => ( is => 'ro' );
+has 'userinfo' => ( is => 'ro' );
+has 'max_content_length' => ( is => 'ro' );
+has 'default_headers' => ( is => 'ro' );
+has 'deflate' => ( is => 'ro' );
+has 'gzip' => ( is => 'ro' );
+has 'ssl_options' => ( is => 'ro', predicate => 'has_ssl_options' );
+has 'handle' => ( is => 'lazy', clearer => 1 );
+has '_pid' => ( is => 'rw', default => $$ );
+
+my %Code_To_Error = (
+ 400 => 'Request',
+ 401 => 'Unauthorized',
+ 403 => 'Forbidden',
+ 404 => 'Missing',
+ 408 => 'RequestTimeout',
+ 409 => 'Conflict',
+ 413 => 'ContentLength',
+ 502 => 'BadGateway',
+ 503 => 'Unavailable',
+ 504 => 'GatewayTimeout'
+);
+
+#===================================
+sub stringify { shift->uri . '' }
+#===================================
+
+#===================================
+sub get_user_agent {
+#===================================
+ return sprintf("elasticsearch-perl/%s (%s; perl %s)", $Search::Elasticsearch::VERSION, $^O, $]);
+}
+
+#===================================
+sub get_meta_header {
+#===================================
+ return sprintf("es=%s,pl=%s", $Search::Elasticsearch::VERSION, $^V);
+}
+
+
+#===================================
+sub BUILDARGS {
+#===================================
+ my ( $class, $params ) = parse_params(@_);
+
+ my $node = $params->{node}
+ || { host => 'localhost', port => '9200' };
+
+ unless ( ref $node eq 'HASH' ) {
+ $node = "[$node]" if Net::IP::ip_is_ipv6($node);
+ unless ( $node =~ m{^http(s)?://} ) {
+ $node = ( $params->{use_https} ? 'https://' : 'http://' ) . $node;
+ }
+ if ( $params->{port} && $node !~ m{//[^/\[]+:\d+} ) {
+ $node =~ s{(//[^/]+)}{$1:$params->{port}};
+ }
+ my $uri = URI->new($node);
+ $node = {
+ scheme => $uri->scheme,
+ host => $uri->host,
+ port => $uri->port,
+ path => $uri->path,
+ userinfo => $uri->userinfo
+ };
+ }
+
+ my $host = $node->{host} || 'localhost';
+ my $userinfo = $node->{userinfo} || $params->{userinfo} || '';
+ my $scheme
+ = $node->{scheme} || ( $params->{use_https} ? 'https' : 'http' );
+ my $port
+ = $node->{port}
+ || $params->{port}
+ || ( $scheme eq 'http' ? 80 : 443 );
+ my $path = $node->{path} || $params->{path_prefix} || '';
+ $path =~ s{^/?}{/}g;
+ $path =~ s{/+$}{};
+
+ my %default_headers = %{ $params->{default_headers} || {} };
+
+ if ($userinfo) {
+ require MIME::Base64;
+ my $auth = MIME::Base64::encode_base64( $userinfo, "" );
+ chomp $auth;
+ $default_headers{Authorization} = "Basic $auth";
+ }
+
+ if ( $params->{gzip} ) {
+ $default_headers{'Accept-Encoding'} = "gzip";
+ }
+
+ elsif ( $params->{deflate} ) {
+ $default_headers{'Accept-Encoding'} = "deflate";
+ }
+
+ $default_headers{'User-Agent'} = $class->get_user_agent();
+
+ # Add Elastic meta header
+ $default_headers{'x-elastic-client-meta'} = $class->get_meta_header();
+
+ # Compatibility header
+ if (defined $ENV{ELASTIC_CLIENT_APIVERSIONING} &&
+ (lc($ENV{ELASTIC_CLIENT_APIVERSIONING}) eq 'true' || $ENV{ELASTIC_CLIENT_APIVERSIONING} eq '1')) {
+ $default_headers{'Accept'} = 'application/vnd.elasticsearch+json;compatible-with=7';
+ $default_headers{'Content-Type'} = 'application/vnd.elasticsearch+json; compatible-with=7';
+ }
+
+ if (defined $params->{elastic_cloud_api_key} && defined $params->{token_api}) {
+ throw( 'Request',
+ "You cannot set elastic_cloud_api_key and token_api together" );
+ }
+
+ # Elastic cloud API key
+ if (defined $params->{elastic_cloud_api_key}) {
+ $default_headers{'Authorization'} = sprintf("ApiKey %s", $params->{elastic_cloud_api_key});
+ }
+
+ # Elasticsearch token API (https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-token.html)
+ if (defined $params->{token_api}) {
+ $default_headers{'Authorization'} = sprintf("Bearer %s", $params->{token_api});
+ }
+
+ # Elasticsearch
+ $params->{scheme} = $scheme;
+ $params->{is_https} = $scheme eq 'https';
+ $params->{host} = $host;
+ $params->{port} = $port;
+ $params->{path} = $path;
+ $params->{userinfo} = $userinfo;
+ $host = "[$host]" if Net::IP::ip_is_ipv6($host);
+ $params->{uri} = URI->new("$scheme://$host:$port$path");
+ $params->{default_headers} = \%default_headers;
+
+ return $params;
+}
+
+#===================================
+before 'handle' => sub {
+#===================================
+ my $self = shift;
+ if ( $$ != $self->_pid ) {
+ $self->clear_handle;
+ $self->_pid($$);
+ }
+};
+
+#===================================
+sub is_live { !shift->next_ping }
+sub is_dead { !!shift->next_ping }
+#===================================
+
+#===================================
+sub mark_live {
+#===================================
+ my $self = shift;
+ $self->ping_failures(0);
+ $self->next_ping(0);
+}
+
+#===================================
+sub mark_dead {
+#===================================
+ my $self = shift;
+ my $fails = $self->ping_failures;
+ $self->ping_failures( $fails + 1 );
+
+ my $timeout
+ = min( $self->dead_timeout * 2**$fails, $self->max_dead_timeout );
+ my $next = $self->next_ping( time() + $timeout );
+
+ $self->logger->infof( 'Marking [%s] as dead. Next ping at: %s',
+ $self->stringify, scalar localtime($next) );
+
+}
+
+#===================================
+sub force_ping {
+#===================================
+ my $self = shift;
+ $self->ping_failures(0);
+ $self->next_ping(-1);
+}
+
+#===================================
+sub pings_ok {
+#===================================
+ my $self = shift;
+ $self->logger->infof( 'Pinging [%s]', $self->stringify );
+ return try {
+ $self->perform_request(
+ { method => 'HEAD',
+ path => '/',
+ timeout => $self->ping_timeout,
+ }
+ );
+ $self->logger->infof( 'Marking [%s] as live', $self->stringify );
+ $self->mark_live;
+ 1;
+ }
+ catch {
+ $self->logger->debug("$_");
+ $self->mark_dead;
+ 0;
+ };
+}
+
+#===================================
+sub sniff {
+#===================================
+ my $self = shift;
+ $self->logger->infof( 'Sniffing [%s]', $self->stringify );
+ return try {
+ $self->perform_request(
+ { method => 'GET',
+ path => '/_nodes/http',
+ qs => { timeout => $self->sniff_timeout . 's' },
+ timeout => $self->sniff_request_timeout,
+ }
+ )->{nodes};
+ }
+ catch {
+ $self->logger->debug($_);
+ return;
+ };
+}
+
+#===================================
+sub build_uri {
+#===================================
+ my ( $self, $params ) = @_;
+ my $uri = $self->uri->clone;
+ $uri->path( $uri->path . $params->{path} );
+ my %qs = ( %{ $self->default_qs_params }, %{ $params->{qs} || {} } );
+ $uri->query_form( \%qs );
+ return $uri;
+}
+
+#===================================
+before 'perform_request' => sub {
+#===================================
+ my ( $self, $params ) = @_;
+ return unless defined $params->{data};
+
+ $self->_compress_body($params);
+
+ my $max = $self->max_content_length
+ or return;
+
+ return if length( $params->{data} ) < $max;
+
+ $self->logger->throw_error( 'ContentLength',
+ "Body is longer than max_content_length ($max)",
+ );
+};
+
+#===================================
+sub _compress_body {
+#===================================
+ my ( $self, $params ) = @_;
+ my $output;
+ if ( $self->gzip ) {
+ IO::Compress::Gzip::gzip( \( $params->{data} ), \$output )
+ or throw( 'Request',
+ "Couldn't gzip request: $IO::Compress::Gzip::GzipError" );
+ $params->{data} = $output;
+ $params->{encoding} = 'gzip';
+ }
+ elsif ( $self->deflate ) {
+ IO::Compress::Deflate::deflate( \( $params->{data} ), \$output )
+ or throw( 'Request',
+ "Couldn't deflate request: $IO::Compress::Deflate::DeflateError" );
+ $params->{data} = $output;
+ $params->{encoding} = 'deflate';
+ }
+}
+
+#===================================
+sub _decompress_body {
+#===================================
+ my ( $self, $body_ref, $headers ) = @_;
+ if ( my $encoding = $headers->{'content-encoding'} ) {
+ my $output;
+ if ( $encoding eq 'gzip' ) {
+ IO::Uncompress::Gunzip::gunzip( $body_ref, \$output )
+ or throw(
+ 'Request',
+ "Couldn't gunzip response: $IO::Uncompress::Gunzip::GunzipError"
+ );
+ }
+ elsif ( $encoding eq 'deflate' ) {
+ IO::Uncompress::Inflate::inflate( $body_ref, \$output,
+ Transparent => 0 )
+ or throw(
+ 'Request',
+ "Couldn't inflate response: $IO::Uncompress::Inflate::InflateError"
+ );
+ }
+ else {
+ throw( 'Request', "Unknown content-encoding: $encoding" );
+ }
+ ${$body_ref} = $output;
+ }
+}
+
+#===================================
+sub process_response {
+#===================================
+ my ( $self, $params, $code, $msg, $body, $headers ) = @_;
+
+ # Product check
+ if ( $code >= 200 and $code < 300 ) {
+ my $product = $headers->{$PRODUCT_CHECK_HEADER} // '';
+ if ($product ne $PRODUCT_CHECK_VALUE) {
+ throw( "ProductCheck", "The client noticed that the server is not Elasticsearch and we do not support this unknown product" );
+ }
+ }
+
+ $self->_decompress_body( \$body, $headers );
+
+ my ($mime_type) = split /\s*;\s*/, ( $headers->{'content-type'} || '' );
+
+ my $is_encoded = $mime_type && $mime_type ne 'text/plain';
+
+ # Deprecation warnings
+ if ( my $warnings = $headers->{warning} ) {
+ my $warning_string = _parse_warnings($warnings);
+ my %temp = (%$params);
+ delete $temp{data};
+ $self->logger->deprecation( $warning_string, \%temp );
+ }
+
+ # Request is successful
+ if ( $code >= 200 and $code <= 209 ) {
+ if ( defined $body and length $body ) {
+ $body = $self->serializer->decode($body)
+ if $is_encoded;
+ return $code, $body;
+ }
+ return ( $code, 1 ) if $params->{method} eq 'HEAD';
+ return ( $code, '' );
+ }
+
+ # Check if the error should be ignored
+ my @ignore = to_list( $params->{ignore} );
+ push @ignore, 404 if $params->{method} eq 'HEAD';
+ return ($code) if grep { $_ eq $code } @ignore;
+
+ # Determine error type
+ my $error_type = $Code_To_Error{$code};
+ unless ($error_type) {
+ if ( defined $body and length $body ) {
+ $msg = $body;
+ $body = undef;
+ }
+ $error_type = $self->error_from_text( $code, $msg );
+ }
+
+ delete $params->{data} if $params->{body};
+ my %error_args = ( status_code => $code, request => $params );
+
+ # Extract error message from the body, if present
+
+ if ( $body = $self->serializer->decode($body) ) {
+ $error_args{body} = $body;
+ $msg = $self->_munge_elasticsearch_exception($body) || $msg;
+
+ $error_args{current_version} = $1
+ if $error_type eq 'Conflict'
+ and $msg =~ /: version conflict, current (?:version )?\[(\d+)\]/;
+ }
+ $msg ||= $error_type;
+
+ chomp $msg;
+ throw( $error_type, "[" . $self->stringify . "]-[$code] $msg",
+ \%error_args );
+}
+
+#===================================
+sub _parse_warnings {
+#===================================
+ my @warnings = ref $_[0] eq 'ARRAY' ? @{ shift() } : shift();
+ my @str;
+ for (@warnings) {
+ if ( $_ =~ /^\d+\s+\S+\s+"((?:\\"|[^"])+)"/ ) {
+ my $msg = $1;
+ $msg =~ s/\\"/"/g, push @str, $msg;
+ }
+ else {
+ push @str, $_;
+ }
+ }
+ return join "; ", @str;
+}
+
+#===================================
+sub _munge_elasticsearch_exception {
+#===================================
+ my ( $self, $body ) = @_;
+ return $body unless ref $body eq 'HASH';
+ my $error = $body->{error} || return;
+ return $error unless ref $error eq 'HASH';
+
+ my $root_causes = $error->{root_cause} || [];
+ unless (@$root_causes) {
+ my $msg = "[" . $error->{type} . "] " if $error->{type};
+ $msg .= $error->{reason} if $error->{reason};
+ return $msg;
+ }
+
+ my $json = $self->serializer;
+ my @msgs;
+ for (@$root_causes) {
+ my %cause = (%$_);
+ my $msg
+ = "[" . ( delete $cause{type} ) . "] " . ( delete $cause{reason} );
+ if ( keys %cause ) {
+ $msg .= ", with: " . $json->encode( \%cause );
+ }
+ push @msgs, $msg;
+ }
+ return ( join ", ", @msgs );
+}
+
+1;
+
+# ABSTRACT: Provides common functionality to HTTP Cxn implementations
+
+=head1 DESCRIPTION
+
+L<Search::Elasticsearch::Role::Cxn> provides common functionality to Cxn
+implementations. Cxn instances are created by a
+L<Search::Elasticsearch::Role::CxnPool> implementation, using the
+L<Search::Elasticsearch::Cxn::Factory> class.
+
+=head1 CONFIGURATION
+
+The configuration options are as follows:
+
+=head2 C<node>
+
+A single C<node> is passed to C<new()> by the L<Search::Elasticsearch::Cxn::Factory>
+class. It can either be a URI or a hash containing each part. For instance:
+
+ node => 'localhost'; # equiv of 'http://localhost:80'
+ node => 'localhost:9200'; # equiv of 'http://localhost:9200'
+ node => 'http://localhost:9200';
+
+ node => 'https://localhost'; # equiv of 'https://localhost:443'
+ node => 'localhost/path'; # equiv of 'http://localhost:80/path'
+
+
+ node => 'http://user:pass@localhost'; # equiv of 'http://localhost:80'
+ # with userinfo => 'user:pass'
+
+Alternatively, a C<node> can be specified as a hash:
+
+ {
+ scheme => 'http',
+ host => 'search.domain.com',
+ port => '9200',
+ path => '/path',
+ userinfo => 'user:pass'
+ }
+
+Similarly, default values can be specified with C<port>, C<path_prefix>,
+C<userinfo> and C<use_https>:
+
+ $e = Search::Elasticsearch->new(
+ port => 9201,
+ path_prefix => '/path',
+ userinfo => 'user:pass',
+ use_https => 1,
+ nodes => [ 'search1', 'search2' ]
+ )
+
+=head2 C<ssl_options>
+
+By default, all backends that support HTTPS disable verification of
+the host they are connecting to. Use C<ssl_options> to configure
+the type of verification that you would like the client to perform,
+or to configure the client to present its own certificate.
+
+The values accepted by C<ssl_options> depend on the C<Cxn> class. See the
+documentation for the C<Cxn> class that you are using.
+
+=head2 C<max_content_length>
+
+By default, Elasticsearch nodes accept a maximum post body of 100MB or
+C<104_857_600> bytes. This client enforces that limit. The limit can
+be customised with the C<max_content_length> parameter (specified in bytes).
+
+If you're using the L<Search::Elasticsearch::CxnPool::Sniff> module, then the
+C<max_content_length> will be automatically retrieved from the live cluster,
+unless you specify a custom C<max_content_length>:
+
+ # max_content_length retrieved from cluster
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Sniff'
+ );
+
+ # max_content_length fixed at 10,000 bytes
+ $e = Search::Elasticsearch->new(
+ cxn_pool => 'Sniff',
+ max_content_length => 10_000
+ );
+
+=head2 C<gzip>
+
+Enable Gzip compression of requests to and responses from Elasticsearch
+as follows:
+
+ $e = Search::Elasticsearch->new(
+ gzip => 1
+ );
+
+=head2 C<deflate>
+
+Enable Inflate/Deflate compression of requests to and responses from Elasticsearch
+as follows:
+
+ $e = Search::Elasticsearch->new(
+ deflate => 1
+ );
+
+
+B<IMPORTANT:> The L</request_timeout>, L</ping_timeout>, L</sniff_timeout>,
+and L</sniff_request_timeout> parameters default to values that allow
+this module to function with low powered hardware and slow networks.
+When you use Elasticsearch in production, you will probably want to reduce
+these timeout parameters to values that suit your environment.
+
+The configuration parameters are as follows:
+
+=head2 C<request_timeout>
+
+ $e = Search::Elasticsearch->new(
+ request_timeout => 30
+ );
+
+How long a normal request (ie not a ping or sniff request) should wait
+before throwing a C<Timeout> error. Defaults to C<30> seconds.
+
+B<Note:> In production, no CRUD or search request should take 30 seconds to run,
+although admin tasks like C<upgrade()>, C<optimize()>, or snapshot C<create()>
+may take much longer. A more reasonable value for production would be
+C<10> seconds or lower.
+
+=head2 C<ping_timeout>
+
+ $e = Search::Elasticsearch->new(
+ ping_timeout => 2
+ );
+
+How long a ping request should wait before throwing a C<Timeout> error.
+Defaults to C<2> seconds. The L<Search::Elasticsearch::CxnPool::Static> module
+pings nodes on first use, after any failure, and periodically to ensure
+that nodes are healthy. The C<ping_timeout> should be long enough to allow
+nodes respond in time, but not so long that sick nodes cause delays.
+A reasonable value for use in production on reasonable hardware
+would be C<0.3>-C<1> seconds.
+
+=head2 C<dead_timeout>
+
+ $e = Search::Elasticsearch->new(
+ dead_timeout => 60
+ );
+
+How long a Cxn should be considered to be I<dead> (not used to serve requests),
+before it is retried. The default is C<60> seconds. This value is increased
+by powers of 2 for each time a request fails. In other words, the delay
+after each failure is as follows:
+
+ Failure Delay
+ 1 60 * 1 = 60 seconds
+ 2 60 * 2 = 120 seconds
+ 3 60 * 4 = 240 seconds
+ 4 60 * 8 = 480 seconds
+ 5 60 * 16 = 960 seconds
+
+=head2 C<max_dead_timeout>
+
+ $e = Search::Elasticsearch->new(
+ max_dead_timeout => 3600
+ );
+
+The maximum delay that should be applied to a failed node. If the
+L</dead_timeout> calculation results in a delay greater than
+C<max_dead_timeout> (default C<3,600> seconds) then the C<max_dead_timeout>
+is used instead. In other words, dead nodes will be retried at least once
+every hour by default.
+
+=head2 C<sniff_request_timeout>
+
+ $e = Search::Elasticsearch->new(
+ sniff_request_timeout => 2
+ );
+
+How long a sniff request should wait before throwing a C<Timeout> error.
+Defaults to C<2> seconds. A reasonable value for production would be
+C<0.5>-C<2> seconds.
+
+=head2 C<sniff_timeout>
+
+ $e = Search::Elasticsearch->new(
+ sniff_timeout => 1
+ );
+
+How long the node being sniffed should wait for responses from other nodes
+before responding to the client. Defaults to C<1> second. A reasonable
+value in production would be C<0.3>-C<1> seconds.
+
+B<Note:> The C<sniff_timeout> is distinct from the L</sniff_request_timeout>.
+For example, let's say you have a cluster with 5 nodes, 2 of which are
+unhealthy (taking a long time to respond):
+
+=over
+
+=item *
+
+If you sniff an unhealthy node, the request will throw a C<Timeout> error
+after C<sniff_request_timeout> seconds.
+
+=item *
+
+If you sniff a healthy node, it will gather responses from the other nodes,
+and give up after C<sniff_timeout> seconds, returning just the information it
+has managed to gather from the healthy nodes.
+
+=back
+
+B<NOTE:> The C<sniff_request_timeout> must be longer than the C<sniff_timeout>
+to ensure that you get information about healthy nodes from the cluster.
+
+=head2 C<handle_args>
+
+Any default arguments which should be passed when creating a new instance of
+the class which handles the network transport, eg L<HTTP::Tiny>.
+
+=head2 C<default_qs_params>
+
+ $e = Search::Elasticsearch->new(
+ default_qs_params => {
+ session_key => 'my_session_key'
+ }
+ );
+
+Any values passed to C<default_qs_params> will be added to the query string
+of every request. Also see L<Search::Elasticsearch::Role::Cxn::HTTP/default_headers()>.
+
+
+=head1 METHODS
+
+None of the methods listed below are useful to the user. They are
+documented for those who are writing alternative implementations only.
+
+=head2 C<scheme()>
+
+ $scheme = $cxn->scheme;
+
+Returns the scheme of the connection, ie C<http> or C<https>.
+
+=head2 C<is_https()>
+
+ $bool = $cxn->is_https;
+
+Returns C<true> or C<false> depending on whether the C</scheme()> is C<https>
+or not.
+
+=head2 C<userinfo()>
+
+ $userinfo = $cxn->userinfo
+
+Returns the username and password of the cxn, if any, eg C<"user:pass">.
+If C<userinfo> is provided, then a Basic Authorization header is added
+to each request.
+
+=head2 C<default_headers()>
+
+ $headers = $cxn->default_headers
+
+The default headers that are passed with each request. This includes
+the C<Accept-Encoding> header if C</deflate> is true, and the C<Authorization>
+header if C</userinfo> has a value.
+Also see L<Search::Elasticsearch::Role::Cxn/default_qs_params>.
+
+=head2 C<max_content_length()>
+
+ $int = $cxn->max_content_length;
+
+Returns the maximum length in bytes that the HTTP body can have.
+
+=head2 C<build_uri()>
+
+ $uri = $cxn->build_uri({ path => '/_search', qs => { size => 10 }});
+
+Returns the HTTP URI to use for a particular request, combining the passed
+in C<path> parameter with any defined C<path_prefix>, and adding the
+query-string parameters.
+
+=head1 METHODS
+
+None of the methods listed below are useful to the user. They are
+documented for those who are writing alternative implementations only.
+
+=head2 C<host()>
+
+ $host = $cxn->host;
+
+The value of the C<host> parameter, eg C<search.domain.com>.
+
+=head2 C<port()>
+
+ $port = $cxn->port;
+
+The value of the C<port> parameter, eg C<9200>.
+
+=head2 C<uri()>
+
+ $uri = $cxn->uri;
+
+A L<URI> object representing the node, eg C<https://search.domain.com:9200/path>.
+
+=head2 C<is_dead()>
+
+ $bool = $cxn->is_dead
+
+Is the current node marked as I<dead>.
+
+=head2 C<is_live()>
+
+ $bool = $cxn->is_live
+
+Is the current node marked as I<live>.
+
+=head2 C<next_ping()>
+
+ $time = $cxn->next_ping($time)
+
+Get/set the time for the next scheduled ping. If zero, no ping is scheduled
+and the cxn is considered to be alive. If -1, a ping is scheduled before
+the next use.
+
+=head2 C<ping_failures()>
+
+ $num = $cxn->ping_failures($num)
+
+The number of times that a cxn has been marked as dead.
+
+=head2 C<mark_dead()>
+
+ $cxn->mark_dead
+
+Mark the cxn as I<dead>, set L</next_ping()> and increment L</ping_failures()>.
+
+=head2 C<mark_live()>
+
+Mark the cxn as I<live>, set L</next_ping()> and L</ping_failures()> to zero.
+
+=head2 C<force_ping()>
+
+Set L</next_ping()> to -1 (ie before next use) and L</ping_failures()> to zero.
+
+=head2 C<pings_ok()>
+
+ $bool = $cxn->pings_ok
+
+Try to ping the node and call L</mark_live()> or L</mark_dead()> depending on
+the success or failure of the ping.
+
+=head2 C<sniff()>
+
+ $response = $cxn->sniff;
+
+Send a sniff request to the node and return the response.
+
+=head2 C<process_response()>
+
+ ($code,$result) = $cxn->process_response($params, $code, $msg, $body );
+
+Processes the response received from an Elasticsearch node and either
+returns the HTTP status code and the response body (deserialized from JSON)
+or throws an error of the appropriate type.
+
+The C<$params> are the original params passed to
+L<Search::Elasticsearch::Transport/perform_request()>, the C<$code> is the HTTP
+status code, the C<$msg> is the error message returned by the backend
+library and the C<$body> is the HTTP response body returned by
+Elasticsearch.
+
diff --git a/lib/Search/Elasticsearch/Role/Cxn/Async.pm b/lib/Search/Elasticsearch/Role/Cxn/Async.pm
new file mode 100644
index 0000000..8d40bad
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Cxn/Async.pm
@@ -0,0 +1,106 @@
+# 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 Search::Elasticsearch::Role::Cxn::Async;
+
+use Moo::Role;
+
+use Search::Elasticsearch::Util qw(new_error);
+use namespace::clean;
+
+#===================================
+sub pings_ok {
+#===================================
+ my $self = shift;
+ $self->logger->infof( 'Pinging [%s]', $self->stringify );
+
+ $self->perform_request(
+ { method => 'HEAD',
+ path => '/',
+ timeout => $self->ping_timeout,
+ }
+ )->then(
+ sub {
+ $self->logger->infof( 'Marking [%s] as live', $self->stringify );
+ $self->mark_live;
+ },
+ sub {
+ $self->logger->debug(@_);
+ $self->mark_dead;
+ die(@_);
+ }
+ );
+}
+
+#===================================
+sub sniff {
+#===================================
+ my $self = shift;
+ $self->logger->infof( 'Sniffing [%s]', $self->stringify );
+ $self->perform_request(
+ { method => 'GET',
+ path => '/_nodes/http',
+ qs => { timeout => $self->sniff_timeout . 's' },
+ timeout => $self->sniff_request_timeout,
+ }
+ )->then(
+ sub { ( $self, $_[1]->{nodes} ) },
+ sub {
+ $self->mark_dead;
+ $self->logger->debug(@_);
+ ($self);
+ }
+ );
+}
+1;
+
+# ABSTRACT: Provides common functionality to async Cxn implementations
+
+=head1 DESCRIPTION
+
+L<Search::Elasticsearch::Role::Cxn::Async> provides common functionality to the
+async Cxn implementations. Cxn instances are created by a
+L<Search::Elasticsearch::Role::CxnPool> implementation,
+using the L<Search::Elasticsearch::Cxn::Factory> class.
+
+=head1 CONFIGURATION
+
+See L<Search::Elasticsearch::Role::Cxn> for configuration options.
+
+=head1 METHODS
+
+None of the methods listed below are useful to the user. They are
+documented for those who are writing alternative implementations only.
+
+
+=head2 C<pings_ok()>
+
+ $promise = $cxn->pings_ok
+
+Try to ping the node and call L</mark_live()> or L</mark_dead()> depending on
+the success or failure of the ping.
+
+=head2 C<sniff()>
+
+ $cxn->sniff
+ ->then(
+ sub { my ($cxn,$nodes) = @_; ... },
+ sub { my $cxn = shift; ... }
+ )
+
+Send a sniff request to the node and return the response.
+
diff --git a/lib/Search/Elasticsearch/Role/CxnPool.pm b/lib/Search/Elasticsearch/Role/CxnPool.pm
new file mode 100644
index 0000000..0f43b90
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/CxnPool.pm
@@ -0,0 +1,290 @@
+# 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 Search::Elasticsearch::Role::CxnPool;
+
+use Moo::Role;
+use Search::Elasticsearch::Util qw(parse_params);
+use List::Util qw(shuffle);
+use IO::Select();
+use Time::HiRes qw(time sleep);
+use Search::Elasticsearch::Util qw(to_list);
+use namespace::clean;
+
+requires qw(next_cxn schedule_check);
+
+has 'cxn_factory' => ( is => 'ro', required => 1 );
+has 'logger' => ( is => 'ro', required => 1 );
+has 'serializer' => ( is => 'ro', required => 1 );
+has 'current_cxn_num' => ( is => 'rwp', default => 0 );
+has 'cxns' => ( is => 'rwp', default => sub { [] } );
+has 'seed_nodes' => ( is => 'ro', required => 1 );
+has 'retries' => ( is => 'rw', default => 0 );
+has 'randomize_cxns' => ( is => 'ro', default => 1 );
+
+#===================================
+around BUILDARGS => sub {
+#===================================
+ my $orig = shift;
+ my $params = $orig->(@_);
+ my @seed = grep {$_} to_list( delete $params->{nodes} || ('') );
+
+ @seed = $params->{cxn_factory}->default_host
+ unless @seed;
+ $params->{seed_nodes} = \@seed;
+ return $params;
+};
+
+#===================================
+sub next_cxn_num {
+#===================================
+ my $self = shift;
+ my $cxns = $self->cxns;
+ return unless @$cxns;
+ my $current = $self->current_cxn_num;
+ $self->_set_current_cxn_num( ( $current + 1 ) % @$cxns );
+ return $current;
+}
+
+#===================================
+sub set_cxns {
+#===================================
+ my $self = shift;
+ my $factory = $self->cxn_factory;
+ my @cxns = map { $factory->new_cxn($_) } @_;
+ @cxns = shuffle @cxns if $self->randomize_cxns;
+ $self->_set_cxns( \@cxns );
+ $self->_set_current_cxn_num(0);
+
+ $self->logger->infof( "Current cxns: %s",
+ [ map { $_->stringify } @cxns ] );
+
+ return;
+}
+
+#===================================
+sub request_ok {
+#===================================
+ my ( $self, $cxn ) = @_;
+ $cxn->mark_live;
+ $self->reset_retries;
+}
+
+#===================================
+sub request_failed {
+#===================================
+ my ( $self, $cxn, $error ) = @_;
+
+ if ( $error->is( 'Cxn', 'Timeout' ) ) {
+ $cxn->mark_dead if $self->should_mark_dead($error);
+ $self->schedule_check;
+
+ if ( $self->should_retry($error) ) {
+ my $retries = $self->retries( $self->retries + 1 );
+ return 1 if $retries < $self->_max_retries;
+ }
+ }
+ else {
+ $cxn->mark_live if $cxn;
+ }
+ $self->reset_retries;
+ return 0;
+}
+
+#===================================
+sub should_retry {
+#===================================
+ my ( $self, $error ) = @_;
+ return $error->is('Cxn');
+}
+
+#===================================
+sub should_mark_dead {
+#===================================
+ my ( $self, $error ) = @_;
+ return $error->is('Cxn');
+}
+
+#===================================
+sub cxns_str {
+#===================================
+ my $self = shift;
+ join ", ", map { $_->stringify } @{ $self->cxns };
+}
+
+#===================================
+sub cxns_seeds_str {
+#===================================
+ my $self = shift;
+ join ", ", ( map { $_->stringify } @{ $self->cxns } ),
+ @{ $self->seed_nodes };
+}
+
+#===================================
+sub reset_retries { shift->retries(0) }
+sub _max_retries {2}
+#===================================
+
+1;
+
+__END__
+
+#ABSTRACT: Provides common functionality to the CxnPool implementations
+
+
+=head1 DESCRIPTION
+
+See the CxnPool implementations:
+
+=over
+
+=item *
+
+L<Search::Elasticsearch::CxnPool::Static>
+
+=item *
+
+L<Search::Elasticsearch::CxnPool::Sniff>
+
+=item *
+
+L<Search::Elasticsearch::CxnPool::Static::NoPing>
+
+=back
+
+=head1 CONFIGURATION
+
+These configuration options should not be set by the user but are
+documented here for completeness.
+
+=head2 C<randomize_cxns>
+
+By default, the order of cxns passed to L</set_cxns()> is randomized
+before they are stored. Set C<randomize_cxns> to a false value to
+disable.
+
+=head1 METHODS
+
+=head2 C<cxn_factory()>
+
+ $factory = $cxn_pool->cxn_factory
+
+Returns the L<Search::Elasticsearch::Cxn::Factory> object for creating a new
+C<$cxn> instance.
+
+=head2 C<logger()>
+
+ $logger = $cxn_pool->logger
+
+Returns the L<Search::Elasticsearch::Role::Logger>-based object, which
+defaults to L<Search::Elasticsearch::Logger::LogAny>.
+
+=head2 C<serializer()>
+
+ $serializer = $cxn_pool->serializer
+
+Returns the L<Search::Elasticsearch::Role::Serializer>-based object,
+which defaults to L<Search::Elasticsearch::Serializer::JSON>.
+
+=head2 C<current_cxn_num()>
+
+ $num = $cxn_pool->current_cxn_num
+
+Returns the current cxn number, which is an offset into
+the array of cxns set by L</set_cxns()>.
+
+=head2 C<cxns()>
+
+ \@cxns = $cxn_pool->cxns;
+
+Returns the current list of L<Search::Elasticsearch::Role::Cxn>-based
+cxn objects as set by L</set_cxns()>.
+
+=head2 C<seed_nodes()>
+
+ \@seed_nodes = $cxn_pool->seed_nodes
+
+Returns the list of C<nodes> originally specified when calling
+L<Search::Elasticsearch/new()>.
+
+=head2 C<next_cxn_num()>
+
+ $num = $cxn_pool->next_cxn_num;
+
+Returns the number of the next connection, in round-robin fashion. Updates
+the L</current_cxn_num()>.
+
+=head2 C<set_cxns()>
+
+ $cxn_pool->set_cxns(@nodes);
+
+Takes a list of nodes, converts them into L<Search::Elasticsearch::Role::Cxn>-based
+objects and makes them accessible via L</cxns()>.
+
+=head2 C<request_ok()>
+
+ $cxn_pool->request_ok($cxn);
+
+Called when a request by the specified C<$cxn> object has completed successfully.
+Marks the C<$cxn> as live.
+
+=head2 C<request_failed()>
+
+ $should_retry = $cxn_pool->request_failed($cxn,$error);
+
+Called when a request by the specified C<$cxn> object has failed. Returns
+C<1> if the request should be retried or C<0> if it shouldn't.
+
+=head2 C<should_retry()>
+
+ $bool = $cxn_pool->should_retry($error);
+
+Examines the error to decide whether the request should be retried or not.
+By default, only L<Search::Elasticsearch::Error/Search::Elasticsearch::Error::Cxn> errors
+are retried.
+
+=head2 C<should_mark_dead()>
+
+ $bool = $cxn_pool->should_mark_dead($error);
+
+Examines the error to decide whether the C<$cxn> should be marked as dead or not.
+By default, only L<Search::Elasticsearch::Error/Search::Elasticsearch::Error::Cxn> errors
+cause a C<$cxn> to be marked as dead.
+
+=head2 C<cxns_str()>
+
+ $str = $cxn_pool->cxns_str
+
+Returns all L</cxns()> as a string for logging purposes.
+
+=head2 C<cxns_seeds_str()>
+
+ $str = $cxn_pool->cxns_seeeds_str
+
+Returns all L</cxns()> and L</seed_nodes()> as a string for logging purposes.
+
+=head2 C<retries()>
+
+ $retries = $cxn_pool->retries
+
+The number of times the current request has been retried.
+
+=head2 C<reset_retries()>
+
+ $cxn_pool->reset_retries;
+
+Called at the start of a new request to reset the retries count.
diff --git a/lib/Search/Elasticsearch/Role/CxnPool/Sniff.pm b/lib/Search/Elasticsearch/Role/CxnPool/Sniff.pm
new file mode 100644
index 0000000..c68c483
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/CxnPool/Sniff.pm
@@ -0,0 +1,170 @@
+# 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 Search::Elasticsearch::Role::CxnPool::Sniff;
+
+use Moo::Role;
+with 'Search::Elasticsearch::Role::CxnPool';
+requires 'next_cxn', 'sniff';
+use namespace::clean;
+
+use Search::Elasticsearch::Util qw(parse_params);
+use List::Util qw(min);
+use Try::Tiny;
+
+has 'sniff_interval' => ( is => 'ro', default => 300 );
+has 'next_sniff' => ( is => 'rw', default => 0 );
+has 'sniff_max_content_length' => ( is => 'ro' );
+
+#===================================
+sub BUILDARGS {
+#===================================
+ my ( $class, $params ) = parse_params(@_);
+ $params->{sniff_max_content_length} = !$params->{max_content_length}
+ unless defined $params->{sniff_max_content_length};
+ return $params;
+}
+
+#===================================
+sub schedule_check {
+#===================================
+ my $self = shift;
+ $self->logger->info("Require sniff before next request");
+ $self->next_sniff(-1);
+}
+
+#===================================
+sub parse_sniff {
+#===================================
+ my $self = shift;
+ my $nodes = shift or return;
+ my @live_nodes;
+ my $max = 0;
+ my $sniff_max = $self->sniff_max_content_length;
+
+ for my $node_id ( keys %$nodes ) {
+ my $data = $nodes->{$node_id};
+
+ my $addr = $data->{http}{publish_address} || $data->{http_address};
+ my $host = $self->_extract_host($addr)
+ or next;
+
+ $host = $self->should_accept_node( $host, $node_id, $data )
+ or next;
+
+ push @live_nodes, $host;
+ next unless $sniff_max and $data->{http};
+
+ my $node_max = $data->{http}{max_content_length_in_bytes} || 0;
+ $max
+ = $node_max == 0 ? $max
+ : $max == 0 ? $node_max
+ : min( $node_max, $max );
+ }
+
+ return unless @live_nodes;
+
+ $self->cxn_factory->max_content_length($max)
+ if $sniff_max and $max;
+
+ $self->set_cxns(@live_nodes);
+ my $next = $self->next_sniff( time() + $self->sniff_interval );
+ $self->logger->infof( "Next sniff at: %s", scalar localtime($next) );
+
+ return 1;
+}
+
+#===================================
+sub _extract_host {
+#===================================
+ my $self = shift;
+ my $host = shift || return;
+ $host =~ s{^inet\[(.+)\]$}{$1};
+ $host =~ s{^[^/]*/}{};
+ return $host;
+}
+
+#===================================
+sub should_accept_node { return $_[1] }
+#===================================
+
+1;
+
+__END__
+
+# ABSTRACT: A CxnPool role for connecting to a local cluster with a dynamic node list
+
+=head1 CONFIGURATION
+
+=head2 C<sniff_interval>
+
+How often should we perform a sniff in order to detect whether new nodes
+have been added to the cluster. Defaults to `300` seconds.
+
+=head2 C<sniff_max_content_length>
+
+Whether we should set the
+L<max_content_length|Search::Elasticsearch::Role::Cxn/max_content_length>
+dynamically while sniffing. Defaults to true unless a fixed
+C<max_content_length> was specified.
+
+=head1 METHODS
+
+=head2 C<schedule_check()>
+
+ $cxn_pool->schedule_check
+
+Schedules a sniff before the next request is processed.
+
+=head2 C<parse_sniff()>
+
+ $bool = $cxn_pool->parse_sniff(\%nodes);
+
+Parses the response from a sniff request and extracts the hostname/ip
+of all listed nodes, filtered through L</should_accept_node()>. If any live
+nodes are found, they are passed to L<Search::Elasticsearch::Role::CxnPool/set_cxns()>.
+The L<max_content_length|Search::Elasticsearch::Role::Cxn/max_content_length>
+is also detected if L</sniff_max_content_length> is true.
+
+=head2 C<should_accept_node()>
+
+ $host = $cxn_pool->should_accept_node($host,$node_id,\%node_data)
+
+This method serves as a hook which can be overridden by the user. When
+a sniff is performed, this method is called with the C<host>
+(eg C<192.168.5.100:9200>), the C<node_id> (the ID assigned to the node
+by Elasticsearch) and the C<node_data> which contains the information
+about the node that Elasticsearch has returned, eg:
+
+ {
+ "transport_address" => "inet[192.168.5.100/192.168.5.100:9300]",
+ "http" : {
+ "publish_address" => "inet[/192.168.5.100:9200]",
+ "max_content_length" => "100mb",
+ "bound_address" => "inet[/0:0:0:0:0:0:0:0:9200]",
+ "max_content_length_in_bytes" : 104857600
+ },
+ "version" => "0.90.4",
+ "name" => "Silver Sable",
+ "hostname" => "search1.domain.com",
+ "http_address" => "inet[/192.168.5.100:9200]"
+ }
+
+If the node should be I<accepted> (ie used to serve data), then it should
+return the C<host> value to use. By default, nodes are always
+accepted.
+
diff --git a/lib/Search/Elasticsearch/Role/CxnPool/Static.pm b/lib/Search/Elasticsearch/Role/CxnPool/Static.pm
new file mode 100644
index 0000000..de50a34
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/CxnPool/Static.pm
@@ -0,0 +1,61 @@
+# 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 Search::Elasticsearch::Role::CxnPool::Static;
+
+use Moo::Role;
+with 'Search::Elasticsearch::Role::CxnPool';
+requires 'next_cxn';
+
+use namespace::clean;
+
+#===================================
+sub BUILD {
+#===================================
+ my $self = shift;
+ $self->set_cxns( @{ $self->seed_nodes } );
+ $self->schedule_check;
+}
+
+#===================================
+sub schedule_check {
+#===================================
+ my ($self) = @_;
+ $self->logger->info("Forcing ping before next use on all live cxns");
+ for my $cxn ( @{ $self->cxns } ) {
+ next if $cxn->is_dead;
+ $self->logger->infof( "Ping [%s] before next request",
+ $cxn->stringify );
+ $cxn->force_ping;
+ }
+}
+
+1;
+
+__END__
+
+# ABSTRACT: A CxnPool role for connecting to a remote cluster with a static list of nodes.
+
+=head1 METHODS
+
+=head2 C<schedule_check()>
+
+ $cxn_pool->schedule_check
+
+Forces a ping on each cxn in L<cxns()|Search::Elasticsearch::Role::CxnPool/cxns()>
+before the next time that cxn is used for a request.
+
diff --git a/lib/Search/Elasticsearch/Role/CxnPool/Static/NoPing.pm b/lib/Search/Elasticsearch/Role/CxnPool/Static/NoPing.pm
new file mode 100644
index 0000000..9589bb0
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/CxnPool/Static/NoPing.pm
@@ -0,0 +1,85 @@
+# 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 Search::Elasticsearch::Role::CxnPool::Static::NoPing;
+
+use Moo::Role;
+with 'Search::Elasticsearch::Role::CxnPool';
+
+use namespace::clean;
+
+has 'max_retries' => ( is => 'lazy' );
+has '_dead_cxns' => ( is => 'ro', default => sub { [] } );
+
+#===================================
+sub next_cxn {
+#===================================
+ my $self = shift;
+
+ my $cxns = $self->cxns;
+ my $total = @$cxns;
+ my $dead = $self->_dead_cxns;
+
+ while ( $total-- ) {
+ my $cxn = $cxns->[ $self->next_cxn_num ];
+ return $cxn
+ if $cxn->is_live
+ || $cxn->next_ping < time();
+ push @$dead, $cxn unless grep { $_ eq $cxn } @$dead;
+ }
+
+ if ( @$dead and $self->retries <= $self->max_retries ) {
+ $_->force_ping for @$dead;
+ return shift @$dead;
+ }
+ throw( "NoNodes", "No nodes are available: [" . $self->cxns_str . ']' );
+}
+
+#===================================
+sub _build_max_retries { @{ shift->cxns } - 1 }
+sub _max_retries { shift->max_retries + 1 }
+#===================================
+
+#===================================
+sub BUILD {
+#===================================
+ my $self = shift;
+ $self->set_cxns( @{ $self->seed_nodes } );
+}
+
+#===================================
+sub should_mark_dead {
+#===================================
+ my ( $self, $error ) = @_;
+ return $error->is( 'Cxn', 'Timeout' );
+}
+
+#===================================
+after 'reset_retries' => sub {
+#===================================
+ my $self = shift;
+ @{ $self->_dead_cxns } = ();
+
+};
+
+#===================================
+sub schedule_check { }
+#===================================
+
+1;
+
+# ABSTRACT: A CxnPool for connecting to a remote cluster without the ability to ping.
diff --git a/lib/Search/Elasticsearch/Role/Is_Async.pm b/lib/Search/Elasticsearch/Role/Is_Async.pm
new file mode 100644
index 0000000..e074e28
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Is_Async.pm
@@ -0,0 +1,25 @@
+# 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 Search::Elasticsearch::Role::Is_Async;
+
+use Moo::Role;
+use namespace::clean;
+
+1;
+
+# ABSTRACT: A role to mark classes which should be used with other async classes
diff --git a/lib/Search/Elasticsearch/Role/Is_Sync.pm b/lib/Search/Elasticsearch/Role/Is_Sync.pm
new file mode 100644
index 0000000..53f376e
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Is_Sync.pm
@@ -0,0 +1,25 @@
+# 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 Search::Elasticsearch::Role::Is_Sync;
+
+use Moo::Role;
+use namespace::clean;
+
+1;
+
+# ABSTRACT: A role to mark classes which should be used with other sync classes
diff --git a/lib/Search/Elasticsearch/Role/Logger.pm b/lib/Search/Elasticsearch/Role/Logger.pm
new file mode 100644
index 0000000..67956f7
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Logger.pm
@@ -0,0 +1,263 @@
+# 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 Search::Elasticsearch::Role::Logger;
+
+use Moo::Role;
+
+use URI();
+use Try::Tiny;
+use Search::Elasticsearch::Util qw(new_error);
+use namespace::clean;
+
+has 'serializer' => ( is => 'ro', required => 1 );
+has 'log_as' => ( is => 'ro', default => 'elasticsearch.event' );
+has 'trace_as' => ( is => 'ro', default => 'elasticsearch.trace' );
+has 'deprecate_as' => ( is => 'ro', default => 'elasticsearch.deprecation' );
+has 'log_to' => ( is => 'ro' );
+has 'trace_to' => ( is => 'ro' );
+has 'deprecate_to' => ( is => 'ro' );
+
+has 'trace_handle' => (
+ is => 'lazy',
+ handles => [qw( trace tracef is_trace)]
+);
+
+has 'log_handle' => (
+ is => 'lazy',
+ handles => [ qw(
+ debug debugf is_debug
+ info infof is_info
+ warning warningf is_warning
+ error errorf is_error
+ critical criticalf is_critical
+ )
+ ]
+);
+
+has 'deprecate_handle' => ( is => 'lazy' );
+
+#===================================
+sub throw_error {
+#===================================
+ my ( $self, $type, $msg, $vars ) = @_;
+ my $error = new_error( $type, $msg, $vars );
+ $self->error($error);
+ die $error;
+}
+
+#===================================
+sub throw_critical {
+#===================================
+ my ( $self, $type, $msg, $vars ) = @_;
+ my $error = new_error( $type, $msg, $vars );
+ $self->critical($error);
+ die $error;
+}
+
+#===================================
+sub trace_request {
+#===================================
+ my ( $self, $cxn, $params ) = @_;
+ return unless $self->is_trace;
+
+ my $uri = URI->new( 'http://localhost:9200' . $params->{path} );
+ my %qs = ( %{ $params->{qs} }, pretty => "true" );
+ $uri->query_form( [ map { $_, $qs{$_} } sort keys %qs ] );
+
+ my $body
+ = $params->{serialize} eq 'std'
+ ? $self->serializer->encode_pretty( $params->{body} )
+ : $params->{data};
+
+ my $content_type = '';
+ if ( defined $body ) {
+ $body =~ s/'/\\u0027/g;
+ $body = " -d '\n$body'\n";
+ $content_type = '-H "Content-type: ' . $params->{mime_type} . '" ';
+ }
+ else { $body = "\n" }
+
+ my $msg = sprintf(
+ "# Request to: %s\n" #
+ . "curl %s-X%s '%s'%s", #
+ $cxn->stringify,
+ $content_type,
+ $params->{method},
+ $uri,
+ $body
+ );
+
+ $self->trace($msg);
+}
+
+#===================================
+sub trace_response {
+#===================================
+ my ( $self, $cxn, $code, $response, $took ) = @_;
+ return unless $self->is_trace;
+
+ my $body = $self->serializer->encode_pretty($response) || "\n";
+ $body =~ s/^/# /mg;
+
+ my $msg = sprintf(
+ "# Response: %s, Took: %d ms\n%s", #
+ $code, $took * 1000, $body
+ );
+
+ $self->trace($msg);
+}
+
+#===================================
+sub trace_error {
+#===================================
+ my ( $self, $cxn, $error ) = @_;
+ return unless $self->is_trace;
+
+ my $body
+ = $self->serializer->encode_pretty( $error->{vars}{body} || "\n" );
+ $body =~ s/^/# /mg;
+
+ my $msg
+ = sprintf( "# ERROR: %s %s\n%s", ref($error), $error->{text}, $body );
+
+ $self->trace($msg);
+}
+
+#===================================
+sub trace_comment {
+#===================================
+ my ( $self, $comment ) = @_;
+ return unless $self->is_trace;
+ $comment =~ s/^/# *** /mg;
+ chomp $comment;
+ $self->trace("$comment\n");
+}
+
+#===================================
+sub deprecation {
+#===================================
+ my $self = shift;
+
+ $self->deprecate_handle->warnf( "[DEPRECATION] %s - In request: %s", @_ );
+}
+1;
+
+# ABSTRACT: Provides common functionality to Logger implementations
+
+=head1 DESCRIPTION
+
+This role provides common functionality to Logger implementations, to enable
+the logging of events and the tracing of request-response conversations
+with Elasticsearch nodes.
+
+See L<Search::Elasticsearch::Logger::LogAny> for the default implementation.
+
+=head1 CONFIGURATION
+
+=head2 C<log_to>
+
+Parameters passed to C<log_to> are used by L<Search::Elasticsearch::Role::Logger>
+implementations to setup the L</log_handle()>. See
+L<Search::Elasticsearch::Logger::LogAny> for details.
+
+=head2 C<log_as>
+
+By default, events emitted by L</debug()>, L</info()>, L</warning()>,
+L</error()> and L</critical()> are logged to the L</log_handle()> under the
+category C<"elasticsearch.event">, which can be configured with C<log_as>.
+
+=head2 C<trace_to>
+
+Parameters passed to C<trace_to> are used by L<Search::Elasticsearch::Role::Logger>
+implementations to setup the L</trace_handle()>. See
+L<Search::Elasticsearch::Logger::LogAny> for details.
+
+=head2 C<trace_as>
+
+By default, trace output emitted by L</trace_request()>, L</trace_response()>,
+L</trace_error()> and L</trace_comment()> are logged under the category
+C<elasticsearch.trace>, which can be configured with C<trace_as>.
+
+=head2 C<deprecate_to>
+
+Parameters passed to C<deprecate_to> are used by L<Search::Elasticsearch::Role::Logger>
+implementations to setup the L</deprecate_handle()>. See
+L<Search::Elasticsearch::Logger::LogAny> for details.
+
+=head2 C<deprecate_as>
+
+By default, events emitted by L</deprecation()> are logged to the
+L</deprecate_handle()> under the
+category C<"elasticsearch.deprecation">, which can be configured with C<deprecate_as>.
+
+
+=head1 METHODS
+
+=head2 C<log_handle()>
+
+Returns an object which can handle the methods:
+C<debug()>, C<debugf()>, C<is_debug()>, C<info()>, C<infof()>, C<is_info()>,
+C<warning()>, C<warningf()>, C<is_warning()>, C<error()>, C<errorf()>,
+C<is_error()>, C<critical()>, C<criticalf()> and C<is_critical()>.
+
+=head2 C<trace_handle()>
+
+Returns an object which can handle the methods:
+C<trace()>, C<tracef()> and C<is_trace()>.
+
+=head2 C<deprecate_handle()>
+
+Returns an object which can handle the C<warnf()> method.
+
+=head2 C<trace_request()>
+
+ $logger->trace_request($cxn,\%request);
+
+Accepts a Cxn object and request parameters and logs them if tracing is
+enabled.
+
+=head2 C<trace_response()>
+
+ $logger->trace_response($cxn,$code,$response,$took);
+
+Logs a successful HTTP response, where C<$code> is the HTTP status code,
+C<$response> is the HTTP body and C<$took> is the time the request
+took in seconds
+
+=head2 C<trace_error()>
+
+ $logger->trace_error($cxn,$error);
+
+Logs a failed HTTP response, where C<$error> is an L<Search::Elasticsearch::Error>
+object.
+
+=head2 C<trace_comment()>
+
+ $logger->trace_comment($comment);
+
+Used to insert debugging comments into trace output.
+
+=head2 C<deprecation()>
+
+ $logger->deprecation($warning,$request)
+
+Issues a deprecation warning to the deprecation logger.
+
+
+
+
diff --git a/lib/Search/Elasticsearch/Role/Serializer.pm b/lib/Search/Elasticsearch/Role/Serializer.pm
new file mode 100644
index 0000000..1b2965f
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Serializer.pm
@@ -0,0 +1,58 @@
+# 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 Search::Elasticsearch::Role::Serializer;
+
+use Moo::Role;
+
+requires qw(encode decode encode_pretty encode_bulk mime_type);
+
+1;
+
+# ABSTRACT: An interface for Serializer modules
+
+=head1 DESCRIPTION
+
+There is no code in this module. It defines an interface for
+Serializer implementations, and requires the following methods:
+
+=over
+
+=item *
+
+C<encode()>
+
+=item *
+
+C<encode_pretty()>
+
+=item *
+
+C<encode_bulk()>
+
+=item *
+
+C<decode()>
+
+=item *
+
+C<mime_type()>
+
+=back
+
+
+See L<Search::Elasticsearch::Serializer::JSON> for more.
diff --git a/lib/Search/Elasticsearch/Role/Serializer/JSON.pm b/lib/Search/Elasticsearch/Role/Serializer/JSON.pm
new file mode 100644
index 0000000..769d119
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Serializer/JSON.pm
@@ -0,0 +1,158 @@
+# 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 Search::Elasticsearch::Role::Serializer::JSON;
+
+use Moo::Role;
+requires 'JSON';
+
+use Search::Elasticsearch::Util qw(throw);
+use Try::Tiny;
+use Encode qw(encode_utf8 decode_utf8 is_utf8);
+use namespace::clean;
+
+has 'mime_type' => ( is => 'ro', default => 'application/json' );
+
+with 'Search::Elasticsearch::Role::Serializer';
+
+#===================================
+sub encode {
+#===================================
+ my ( $self, $var ) = @_;
+ unless ( ref $var ) {
+ return is_utf8($var)
+ ? encode_utf8($var)
+ : $var;
+ }
+ return try { $self->JSON->encode($var) }
+ catch { throw( "Serializer", $_, { var => $var } ) };
+}
+
+#===================================
+sub encode_bulk {
+#===================================
+ my ( $self, $var ) = @_;
+ unless ( ref $var ) {
+ return is_utf8($var)
+ ? encode_utf8($var)
+ : $var;
+ }
+
+ my $json = '';
+ throw( "Param", "Var must be an array ref" )
+ unless ref $var eq 'ARRAY';
+ return try {
+ for (@$var) {
+ $json .= ( ref($_) ? $self->JSON->encode($_) : $_ ) . "\n";
+ }
+ return $json;
+ }
+ catch { throw( "Serializer", $_, { var => $var } ) };
+}
+
+#===================================
+sub encode_pretty {
+#===================================
+ my ( $self, $var ) = @_;
+ $self->JSON->pretty(1);
+
+ my $json;
+ try {
+ $json = $self->encode($var);
+ }
+ catch {
+ die "$_";
+ }
+ finally {
+ $self->JSON->pretty(0);
+ };
+
+ return $json;
+}
+
+#===================================
+sub decode {
+#===================================
+ my ( $self, $json ) = @_;
+
+ return unless defined $json;
+
+ return is_utf8($json) ? $json : decode_utf8($json)
+ unless substr( $json, 0, 1 ) =~ /^[\[{]/;
+
+ return try {
+ $self->JSON->decode($json);
+ }
+ catch {
+ throw( "Serializer", $_, { json => $json } );
+ };
+}
+
+#===================================
+sub _set_canonical {
+#===================================
+ shift()->JSON->canonical(1);
+}
+
+1;
+
+__END__
+
+# ABSTRACT: A Serializer role for JSON modules
+
+=head1 DESCRIPTION
+
+This role encodes Perl data structures into JSON strings, and
+decodes JSON strings into Perl data structures.
+
+=head1 METHODS
+
+=head2 C<encode()>
+
+ $bytes = $serializer->encode($ref);
+ $bytes = $serializer->encode($str);
+
+The L</encode()> method converts array and hash refs into their JSON
+equivalents. If a string is passed in, it is returned as the UTF8 encoded
+version of itself. The empty string and C<undef> are returned as is.
+
+=head2 C<encode_pretty()>
+
+ $bytes = $serializer->encode_pretty($ref);
+ $bytes = $serializer->encode_pretty($str);
+
+Works exactly as L</encode()> but the JSON output is pretty-printed.
+
+=head2 C<encode_bulk()>
+
+ $bytes = $serializer->encode_bulk([\%hash,\%hash,...]);
+ $bytes = $serializer->encode_bulk([$str,$str,...]);
+
+The L</encode_bulk()> method expects an array ref of hashes or strings.
+Each hash or string is processed by L</encode()> then joined together
+by newline characters, with a final newline character appended to the end.
+This is the special JSON format used for bulk requests.
+
+=head2 C<decode()>
+
+ $var = $serializer->decode($json_bytes);
+ $str = $serializer->decode($bytes);
+
+If the passed in value looks like JSON (ie starts with a C<{> or C<[>
+character), then it is decoded from JSON, otherwise it is returned as
+the UTF8 decoded version of itself. The empty string and C<undef> are
+returned as is.
diff --git a/lib/Search/Elasticsearch/Role/Transport.pm b/lib/Search/Elasticsearch/Role/Transport.pm
new file mode 100644
index 0000000..be3b844
--- /dev/null
+++ b/lib/Search/Elasticsearch/Role/Transport.pm
@@ -0,0 +1,65 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Role::Transport;
+
+use Moo::Role;
+
+requires qw(perform_request);
+
+use Try::Tiny;
+use Search::Elasticsearch::Util qw(parse_params is_compat);
+use namespace::clean;
+
+has 'serializer' => ( is => 'ro', required => 1 );
+has 'logger' => ( is => 'ro', required => 1 );
+has 'send_get_body_as' => ( is => 'ro', default => 'GET' );
+has 'cxn_pool' => ( is => 'ro', required => 1 );
+
+#===================================
+sub BUILD {
+#===================================
+ my $self = shift;
+ my $pool = $self->cxn_pool;
+ is_compat( 'cxn_pool', $self, $pool );
+ is_compat( 'cxn', $self, $pool->cxn_factory->cxn_class );
+ return $self;
+}
+
+#===================================
+sub tidy_request {
+#===================================
+ my ( $self, $params ) = parse_params(@_);
+ $params->{method} ||= 'GET';
+ $params->{path} ||= '/';
+ $params->{qs} ||= {};
+ $params->{ignore} ||= [];
+ my $body = $params->{body};
+ return $params unless defined $body;
+
+ $params->{serialize} ||= 'std';
+ $params->{data}
+ = $params->{serialize} eq 'std'
+ ? $self->serializer->encode($body)
+ : $self->serializer->encode_bulk($body);
+
+ if ( $params->{method} eq 'GET' ) {
+ my $send_as = $self->send_get_body_as;
+ if ( $send_as eq 'POST' ) {
+ $params->{method} = 'POST';
+ }
+ elsif ( $send_as eq 'source' ) {
+ $params->{qs}{source} = delete $params->{data};
+ delete $params->{body};
+ }
+ }
+
+ $params->{mime_type} ||= $self->serializer->mime_type;
+ return $params;
+
+}
+
+1;
+
+#ABSTRACT: Transport role providing interface between the client class and the Elasticsearch cluster
diff --git a/lib/Search/Elasticsearch/Serializer/JSON.pm b/lib/Search/Elasticsearch/Serializer/JSON.pm
new file mode 100644
index 0000000..35f129e
--- /dev/null
+++ b/lib/Search/Elasticsearch/Serializer/JSON.pm
@@ -0,0 +1,70 @@
+# 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 Search::Elasticsearch::Serializer::JSON;
+
+use Moo;
+use JSON::MaybeXS 1.002002 ();
+
+has 'JSON' => ( is => 'ro', default => sub { JSON::MaybeXS->new->utf8(1) } );
+
+with 'Search::Elasticsearch::Role::Serializer::JSON';
+use namespace::clean;
+
+1;
+
+# ABSTRACT: The default JSON Serializer, using JSON::MaybeXS
+
+=head1 SYNOPSIS
+
+ $e = Search::Elasticsearch(
+ # serializer => 'JSON'
+ );
+
+=head1 DESCRIPTION
+
+This default Serializer class chooses between:
+
+=over
+
+=item * L<Cpanel::JSON::XS>
+
+=item * L<JSON::XS>
+
+=item * L<JSON::PP>
+
+=back
+
+First it checks if either L<Cpanel::JSON::XS> or L<JSON::XS> is already
+loaded and, if so, uses the appropriate backend. Otherwise it tries
+to load L<Cpanel::JSON::XS>, then L<JSON::XS> and finally L<JSON::PP>.
+
+If you would prefer to specify a particular JSON backend, then you can
+do so by using one of these modules:
+
+=over
+
+=item * L<Search::Elasticsearch::Serializer::JSON::Cpanel>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::XS>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::PP>
+
+=back
+
+See their documentation for details.
+
diff --git a/lib/Search/Elasticsearch/Serializer/JSON/Cpanel.pm b/lib/Search/Elasticsearch/Serializer/JSON/Cpanel.pm
new file mode 100644
index 0000000..e900c34
--- /dev/null
+++ b/lib/Search/Elasticsearch/Serializer/JSON/Cpanel.pm
@@ -0,0 +1,58 @@
+# 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 Search::Elasticsearch::Serializer::JSON::Cpanel;
+
+use Cpanel::JSON::XS;
+use Moo;
+
+has 'JSON' =>
+ ( is => 'ro', default => sub { Cpanel::JSON::XS->new->utf8(1) } );
+
+with 'Search::Elasticsearch::Role::Serializer::JSON';
+
+1;
+
+__END__
+
+# ABSTRACT: A JSON Serializer using Cpanel::JSON::XS
+
+=head1 SYNOPSIS
+
+ $e = Search::Elasticsearch(
+ serializer => 'JSON::Cpanel'
+ );
+
+=head1 DESCRIPTION
+
+While the default serializer, L<Search::Elasticsearch::Serializer::JSON>,
+tries to choose the appropriate JSON backend, this module allows you to
+choose the L<Cpanel::JSON::XS> backend specifically.
+
+This class does L<Search::Elasticsearch::Role::Serializer::JSON>.
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Serializer::JSON>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::XS>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::PP>
+
+=back
diff --git a/lib/Search/Elasticsearch/Serializer/JSON/PP.pm b/lib/Search/Elasticsearch/Serializer/JSON/PP.pm
new file mode 100644
index 0000000..4f42596
--- /dev/null
+++ b/lib/Search/Elasticsearch/Serializer/JSON/PP.pm
@@ -0,0 +1,58 @@
+# 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 Search::Elasticsearch::Serializer::JSON::PP;
+
+use Moo;
+use JSON::PP;
+
+has 'JSON' => ( is => 'ro', default => sub { JSON::PP->new->utf8(1) } );
+
+with 'Search::Elasticsearch::Role::Serializer::JSON';
+
+1;
+
+# ABSTRACT: A JSON Serializer using JSON::PP
+
+=head1 SYNOPSIS
+
+ $e = Search::Elasticsearch(
+ serializer => 'JSON::PP'
+ );
+
+=head1 DESCRIPTION
+
+While the default serializer, L<Search::Elasticsearch::Serializer::JSON>,
+tries to choose the appropriate JSON backend, this module allows you to
+choose the L<JSON::PP> backend specifically.
+
+B<NOTE:> You should really install and use either L<JSON::XS> or
+L<Cpanel::JSON::XS> as they are much much faster than L<JSON::PP>.
+
+This class does L<Search::Elasticsearch::Role::Serializer::JSON>.
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Serializer::JSON>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::XS>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::Cpanel>
+
+=back
diff --git a/lib/Search/Elasticsearch/Serializer/JSON/XS.pm b/lib/Search/Elasticsearch/Serializer/JSON/XS.pm
new file mode 100644
index 0000000..49bb97b
--- /dev/null
+++ b/lib/Search/Elasticsearch/Serializer/JSON/XS.pm
@@ -0,0 +1,57 @@
+# 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 Search::Elasticsearch::Serializer::JSON::XS;
+
+use Moo;
+use JSON::XS 2.26;
+
+has 'JSON' => ( is => 'ro', default => sub { JSON::XS->new->utf8(1) } );
+
+with 'Search::Elasticsearch::Role::Serializer::JSON';
+
+1;
+
+__END__
+
+# ABSTRACT: A JSON Serializer using JSON::XS
+
+=head1 SYNOPSIS
+
+ $e = Search::Elasticsearch(
+ serializer => 'JSON::XS'
+ );
+
+=head1 DESCRIPTION
+
+While the default serializer, L<Search::Elasticsearch::Serializer::JSON>,
+tries to choose the appropriate JSON backend, this module allows you to
+choose the L<JSON::XS> backend specifically.
+
+This class does L<Search::Elasticsearch::Role::Serializer::JSON>.
+
+=head1 SEE ALSO
+
+=over
+
+=item * L<Search::Elasticsearch::Serializer::JSON>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::Cpanel>
+
+=item * L<Search::Elasticsearch::Serializer::JSON::PP>
+
+=back
diff --git a/lib/Search/Elasticsearch/TestServer.pm b/lib/Search/Elasticsearch/TestServer.pm
new file mode 100644
index 0000000..59f486a
--- /dev/null
+++ b/lib/Search/Elasticsearch/TestServer.pm
@@ -0,0 +1,288 @@
+# 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 Search::Elasticsearch::TestServer;
+
+use Moo;
+use Search::Elasticsearch();
+use POSIX 'setsid';
+use File::Temp();
+use IO::Socket();
+use HTTP::Tiny;
+
+use Search::Elasticsearch::Util qw(parse_params throw);
+use namespace::clean;
+
+has 'es_home' => ( is => 'ro', default => $ENV{ES_HOME} );
+has 'es_version' => ( is => 'ro', default => $ENV{ES_VERSION} );
+has 'instances' => ( is => 'ro', default => 1 );
+has 'http_port' => ( is => 'ro', default => 9600 );
+has 'es_port' => ( is => 'ro', default => 9700 );
+has 'pids' => (
+ is => 'ro',
+ default => sub { [] },
+ clearer => 1,
+ predicate => 1
+);
+
+has 'dirs' => ( is => 'ro', default => sub { [] } );
+has 'conf' => ( is => 'ro', default => sub { [] } );
+has '_starter_pid' => ( is => 'rw', required => 0, predicate => 1 );
+
+#===================================
+sub start {
+#===================================
+ my $self = shift;
+
+ my $home = $self->es_home
+ or throw( 'Param', "Missing required param <es_home>" );
+ $self->es_version
+ or throw( 'Param', "Missing required param <es_version>" );
+
+ my $instances = $self->instances;
+ my $port = $self->http_port;
+ my $es_port = $self->es_port;
+ my @http = map { $port++ } ( 1 .. $instances );
+ my @transport = map { $es_port++ } ( 1 .. $instances );
+
+ $self->_check_ports( @http, @transport );
+
+ my $old_SIGINT = $SIG{INT};
+ $SIG{INT} = sub {
+ $self->shutdown;
+ if ( ref $old_SIGINT eq 'CODE' ) {
+ return $old_SIGINT->();
+ }
+ exit(1);
+ };
+
+ for ( 0 .. $instances - 1 ) {
+ my $dir = File::Temp->newdir();
+ push @{ $self->dirs }, $dir;
+ print "Starting node: http://127.0.0.1:$http[$_]\n";
+ $self->_start_node( $dir, $transport[$_], $http[$_] );
+ }
+
+ $self->_check_nodes(@http);
+ return [ map {"http://127.0.0.1:$_"} @http ];
+}
+
+#===================================
+sub _check_ports {
+#===================================
+ my $self = shift;
+ for my $port (@_) {
+ next unless IO::Socket::INET->new("127.0.0.1:$port");
+ throw( 'Param',
+ "There is already a service running on 127.0.0.1:$port. "
+ . "Please shut it down before starting the test server" );
+ }
+}
+
+#===================================
+sub _check_nodes {
+#===================================
+ my $self = shift;
+ my $http = HTTP::Tiny->new;
+ for my $node (@_) {
+ print "Checking node: http://127.0.0.1:$node\n";
+ my $i = 20;
+ while (1) {
+ last
+ if $http->head("http://127.0.0.1:$node/")->{status} == 200;
+ throw( 'Cxn', "Couldn't connect to http://127.0.0.1:$node" )
+ unless $i--;
+ sleep 1;
+ }
+
+ }
+}
+
+#===================================
+sub _start_node {
+#===================================
+ my ( $self, $dir, $transport, $http ) = @_;
+
+ my $pid_file = File::Temp->new;
+ my @config = $self->_command_line( $pid_file, $dir, $transport, $http );
+
+ my $int_caught = 0;
+ {
+ local $SIG{INT} = sub { $int_caught++; };
+ defined( my $pid = fork )
+ or throw( 'Internal', "Couldn't fork a new process: $!" );
+ if ( $pid == 0 ) {
+ throw( 'Internal', "Can't start a new session: $!" )
+ if setsid == -1;
+ exec(@config) or die "Couldn't execute @config: $!";
+ }
+ else {
+ for ( 1 .. 5 ) {
+ last if -s $pid_file->filename();
+ sleep 1;
+ }
+ open my $pid_fh, '<', $pid_file->filename;
+ my $pid = <$pid_fh>;
+ throw( 'Internal', "No PID file found for Elasticsearch" )
+ unless $pid;
+ chomp $pid;
+ push @{ $self->{pids} }, $pid;
+ $self->_starter_pid($$);
+ }
+ }
+ $SIG{INT}->('INT') if $int_caught;
+}
+
+#===================================
+sub guarded_shutdown {
+#===================================
+ my $self = shift;
+ if ( $self->_has_starter_pid && $$ == $self->_starter_pid ) {
+ $self->shutdown();
+ }
+}
+
+#===================================
+sub shutdown {
+#===================================
+ my $self = shift;
+ local $?;
+
+ return unless $self->has_pids;
+
+ my $pids = $self->pids;
+ $self->clear_pids;
+ return unless @$pids;
+
+ kill 9, @$pids;
+ $self->clear_dirs;
+}
+
+#===================================
+sub _command_line {
+#===================================
+ my ( $self, $pid_file, $dir, $transport, $http ) = @_;
+
+ my $version = $self->es_version;
+ my $class = "Search::Elasticsearch::Client::${version}::TestServer";
+ eval "require $class" || die $@;
+
+ return $class->command_line(@_);
+}
+
+#===================================
+sub clear_dirs {
+#===================================
+ my $self = shift;
+ @{ $self->dirs() } = ();
+}
+
+#===================================
+sub DEMOLISH { shift->guarded_shutdown }
+#===================================
+
+1;
+
+# ABSTRACT: A helper class to launch Elasticsearch nodes
+
+=head1 DESCRIPTION
+
+The L<Search::Elasticsearch::TestServer> class can be used to launch one or more
+instances of Elasticsearch for testing purposes. The nodes will
+be shutdown automatically.
+
+=head1 SYNOPSIS
+
+ use Search::Elasticsearch;
+ use Search::Elasticsearch::TestServer;
+
+ my $server = Search::Elasticsearch::TestServer->new(
+ es_home => '/path/to/elasticsearch', # defaults to $ENV{ES_HOME}
+ es_version => '6_0' # defaults to $ENV{ES_VERSION}
+ );
+
+ my $nodes = $server->start;
+ my $es = Search::Elasticsearch->new( nodes => $nodes );
+ # run tests
+ $server->shutdown;
+
+=head1 METHODS
+
+=head2 C<new()>
+
+ my $server = Search::Elasticsearch::TestServer->new(
+ es_home => '/path/to/elasticsearch',
+ es_version => '6_0',
+ instances => 1,
+ http_port => 9600,
+ es_port => 9700,
+ conf => ['attr.foo=bar'],
+ );
+
+Params:
+
+=over
+
+=item * C<es_home>
+
+Required. Must point to the Elasticsearch home directory, which contains
+C<./bin/elasticsearch>. Defaults to C<$ENV{ES_HOME}>
+
+=item * C<es_version>
+
+Required. Accepts a version of the client, eg `6_0`, `5_0`, `2_0`, `1_0`, `0_90`.
+Defaults to C<$ENV{ES_VERSION}>.
+
+=item * C<instances>
+
+The number of nodes to start. Defaults to 1
+
+=item * C<http_port>
+
+The port to use for HTTP. If multiple instances are started, the C<http_port>
+will be incremented for each subsequent instance. Defaults to 9600.
+
+=item * C<es_port>
+
+The port to use for Elasticsearch's internal transport. If multiple instances
+are started, the C<es_port> will be incremented for each subsequent instance.
+Defaults to 9700
+
+=item * C<conf>
+
+An array containing any extra startup options that should be passed
+to Elasticsearch.
+
+=back
+
+=head1 C<start()>
+
+ $nodes = $server->start;
+
+Starts the required instances and returns an array ref containing the IP
+and port of each node, suitable for passing to L<Search::Elasticsearch/new()>:
+
+ $es = Search::Elasticsearch->new( nodes => $nodes );
+
+=head1 C<shutdown()>
+
+ $server->shutdown;
+
+Kills the running instances. This will be called automatically when
+C<$server> goes out of scope or if the program receives a C<SIGINT>.
+
+
diff --git a/lib/Search/Elasticsearch/Transport.pm b/lib/Search/Elasticsearch/Transport.pm
new file mode 100644
index 0000000..7381f77
--- /dev/null
+++ b/lib/Search/Elasticsearch/Transport.pm
@@ -0,0 +1,150 @@
+# Licensed to Elasticsearch B.V under one or more agreements.
+# Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
+# See the LICENSE file in the project root for more information
+
+package Search::Elasticsearch::Transport;
+
+use Moo;
+
+use URI();
+use Time::HiRes qw(time);
+use Try::Tiny;
+use Search::Elasticsearch::Util qw(upgrade_error);
+use namespace::clean;
+
+with 'Search::Elasticsearch::Role::Is_Sync',
+ 'Search::Elasticsearch::Role::Transport';
+
+#===================================
+sub perform_request {
+#===================================
+ my $self = shift;
+ my $params = $self->tidy_request(@_);
+ my $pool = $self->cxn_pool;
+ my $logger = $self->logger;
+
+ my ( $code, $response, $cxn, $error );
+
+ try {
+ $cxn = $pool->next_cxn;
+ my $start = time();
+ $logger->trace_request( $cxn, $params );
+
+ ( $code, $response ) = $cxn->perform_request($params);
+ $pool->request_ok($cxn);
+ $logger->trace_response( $cxn, $code, $response, time() - $start );
+ }
+ catch {
+ $error = upgrade_error(
+ $_,
+ { request => $params,
+ status_code => $code,
+ body => $response
+ }
+ );
+ };
+
+ if ($error) {
+ if ( $pool->request_failed( $cxn, $error ) ) {
+ $logger->debugf( "[%s] %s", $cxn->stringify, "$error" );
+ $logger->info('Retrying request on a new cxn');
+ return $self->perform_request($params);
+ }
+
+ $logger->trace_error( $cxn, $error );
+ $error->is('NoNodes')
+ ? $logger->throw_critical($error)
+ : $logger->throw_error($error);
+ }
+
+ return $response;
+}
+
+1;
+
+#ABSTRACT: Provides interface between the client class and the Elasticsearch cluster
+
+=head1 DESCRIPTION
+
+The Transport class manages the request cycle. It receives parsed requests
+from the (user-facing) client class, and tries to execute the request on a
+node in the cluster, retrying a request if necessary.
+
+This class does L<Search::Elasticsearch::Role::Transport> and
+L<Search::Elasticsearch::Role::Is_Sync>.
+
+=head1 CONFIGURATION
+
+=head2 C<send_get_body_as>
+
+ $e = Search::Elasticsearch->new(
+ send_get_body_as => 'POST'
+ );
+
+Certain endpoints like L<Search::Elasticsearch::Client::6_0::Direct/search()>
+default to using a C<GET> method, even when they include a request body.
+Some proxy servers do not support C<GET> requests with a body. To work
+around this, the C<send_get_body_as> parameter accepts the following:
+
+=over
+
+=item * C<GET>
+
+The default. Request bodies are sent as C<GET> requests.
+
+=item * C<POST>
+
+The method is changed to C<POST> when a body is present.
+
+=item * C<source>
+
+The body is encoded as JSON and added to the query string as the C<source>
+parameter. This has the advantage of still being a C<GET> request (for those
+filtering on request method) but has the disadvantage of being restricted
+in size. The limit depends on the proxies between the client and
+Elasticsearch, but usually is around 4kB.
+
+=back
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+Raw requests can be executed using the transport class as follows:
+
+ $result = $e->transport->perform_request(
+ method => 'POST',
+ path => '/_search',
+ qs => { from => 0, size => 10 },
+ body => {
+ query => {
+ match => {
+ title => "Elasticsearch clients"
+ }
+ }
+ }
+ );
+
+Other than the C<method>, C<path>, C<qs> and C<body> parameters, which
+should be self-explanatory, it also accepts:
+
+=over
+
+=item C<ignore>
+
+The HTTP error codes which should be ignored instead of throwing an error,
+eg C<404 NOT FOUND>:
+
+ $result = $e->transport->perform_request(
+ method => 'GET',
+ path => '/index/type/id'
+ ignore => [404],
+ );
+
+=item C<serialize>
+
+Whether the C<body> should be serialized in the standard way (as plain
+JSON) or using the special I<bulk> format: C<"std"> or C<"bulk">.
+
+=back
+
diff --git a/lib/Search/Elasticsearch/Transport/Async.pm b/lib/Search/Elasticsearch/Transport/Async.pm
new file mode 100644
index 0000000..85463b8
--- /dev/null
+++ b/lib/Search/Elasticsearch/Transport/Async.pm
@@ -0,0 +1,180 @@
+# 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 Search::Elasticsearch::Transport::Async;
+
+use Moo;
+with 'Search::Elasticsearch::Role::Is_Async',
+ 'Search::Elasticsearch::Role::Transport';
+
+use Time::HiRes qw(time);
+use Search::Elasticsearch::Util qw(upgrade_error);
+use Promises qw(deferred);
+use namespace::clean;
+
+#===================================
+sub perform_request {
+#===================================
+ my $self = shift;
+ my $params = $self->tidy_request(@_);
+ my $pool = $self->cxn_pool;
+ my $logger = $self->logger;
+
+ my $deferred = deferred;
+
+ my ( $start, $cxn );
+ $pool->next_cxn
+
+ # perform request
+ ->then(
+ sub {
+ $cxn = shift;
+ $start = time();
+ $cxn->perform_request($params);
+ }
+ )
+
+ # log request regardless of success/failure
+ ->finally( sub { $logger->trace_request( $cxn, $params ) } )
+
+ ->done(
+ # request succeeded
+ sub {
+ my ( $code, $response ) = @_;
+ $pool->request_ok($cxn);
+ $logger->trace_response( $cxn, $code, $response,
+ time() - $start );
+ $deferred->resolve($response);
+ },
+
+ # request failed
+ sub {
+ my $error = upgrade_error( shift(), { request => $params } );
+ if ( $pool->request_failed( $cxn, $error ) ) {
+
+ # log failed, then retry
+ $logger->debugf( "[%s] %s", $cxn->stringify, "$error" );
+ $logger->info('Retrying request on a new cxn');
+ return $self->perform_request($params)->done(
+ sub { $deferred->resolve(@_) },
+ sub { $deferred->reject(@_) }
+ );
+ }
+ if ($cxn) {
+ $logger->trace_request( $cxn, $params );
+ $logger->trace_error( $cxn, $error );
+ }
+ $error->is('NoNodes')
+ ? $logger->critical($error)
+ : $logger->error($error);
+ $deferred->reject($error);
+ }
+ );
+ return $deferred->promise;
+}
+
+1;
+
+#ABSTRACT: Provides async interface between the client class and the Elasticsearch cluster
+
+=head1 DESCRIPTION
+
+The Async::Transport class manages the request cycle. It receives parsed requests
+from the (user-facing) client class, and tries to execute the request on a
+node in the cluster, retrying a request if necessary.
+
+This class does L<Search::Elasticsearch::Role::Transport> and
+L<Search::Elasticsearch::Role::Is_Async>.
+
+=head1 CONFIGURATION
+
+=head2 C<send_get_body_as>
+
+ $e = Search::Elasticsearch::Async->new(
+ send_get_body_as => 'POST'
+ );
+
+Certain endpoints like L<Search::Elasticsearch::Client::6_0::Direct/search()>
+default to using a C<GET> method, even when they include a request body.
+Some proxy servers do not support C<GET> requests with a body. To work
+around this, the C<send_get_body_as> parameter accepts the following:
+
+=over
+
+=item * C<GET>
+
+The default. Request bodies are sent as C<GET> requests.
+
+=item * C<POST>
+
+The method is changed to C<POST> when a body is present.
+
+=item * C<source>
+
+The body is encoded as JSON and added to the query string as the C<source>
+parameter. This has the advantage of still being a C<GET> request (for those
+filtering on request method) but has the disadvantage of being restricted
+in size. The limit depends on the proxies between the client and
+Elasticsearch, but usually is around 4kB.
+
+=back
+
+=head1 METHODS
+
+=head2 C<perform_request()>
+
+Raw requests can be executed using the transport class as follows:
+
+ $promise = $e->transport->perform_request(
+ method => 'POST',
+ path => '/_search',
+ qs => { from => 0, size => 10 },
+ body => {
+ query => {
+ match => {
+ title => "Elasticsearch clients"
+ }
+ }
+ }
+ );
+
+C<perform_request()> returns a L<Promise> object, which will be resolved
+(success) or rejected (error) at some point in the future.
+
+Other than the C<method>, C<path>, C<qs> and C<body> parameters, which
+should be self-explanatory, it also accepts:
+
+=over
+
+=item C<ignore>
+
+The HTTP error codes which should be ignored instead of throwing an error,
+eg C<404 NOT FOUND>:
+
+ $promise = $e->transport->perform_request(
+ method => 'GET',
+ path => '/index/type/id'
+ ignore => [404],
+ );
+
+=item C<serialize>
+
+Whether the C<body> should be serialized in the standard way (as plain
+JSON) or using the special I<bulk> format: C<"std"> or C<"bulk">.
+
+=back
+
diff --git a/lib/Search/Elasticsearch/Util.pm b/lib/Search/Elasticsearch/Util.pm
new file mode 100644
index 0000000..fc96adc
--- /dev/null
+++ b/lib/Search/Elasticsearch/Util.pm
@@ -0,0 +1,125 @@
+# 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 Search::Elasticsearch::Util;
+
+use Moo;
+use Search::Elasticsearch::Error();
+use Scalar::Util qw(blessed);
+use Module::Runtime qw(compose_module_name is_module_name use_module);
+use Sub::Exporter -setup => {
+ exports => [ qw(
+ parse_params
+ to_list
+ load_plugin
+ new_error
+ throw
+ upgrade_error
+ is_compat
+ )
+ ]
+};
+
+#===================================
+sub to_list {
+#===================================
+ grep {defined} ref $_[0] eq 'ARRAY' ? @{ $_[0] } : @_;
+}
+
+#===================================
+sub parse_params {
+#===================================
+ my $self = shift;
+ my %params;
+ if ( @_ % 2 ) {
+ throw(
+ "Param",
+ 'Expecting a HASH ref or a list of key-value pairs',
+ { params => \@_ }
+ ) unless ref $_[0] eq 'HASH';
+ %params = %{ shift() };
+ }
+ else {
+ %params = @_;
+ }
+ return ( $self, \%params );
+}
+
+#===================================
+sub load_plugin {
+#===================================
+ my ( $base, $spec ) = @_;
+ $spec ||= "+$base";
+ return $spec if blessed $spec;
+
+ my ( $class, $version );
+ if ( ref $spec eq 'ARRAY' ) {
+ ( $class, $version ) = @$spec;
+ }
+ else {
+ $class = $spec;
+ }
+
+ unless ( $class =~ s/\A\+// ) {
+ $class = compose_module_name( $base, $class );
+ }
+
+ $version ? use_module( $class, $version ) : use_module($class);
+}
+
+#===================================
+sub throw {
+#===================================
+ my ( $type, $msg, $vars ) = @_;
+ die Search::Elasticsearch::Error->new( $type, $msg, $vars, 1 );
+}
+
+#===================================
+sub new_error {
+#===================================
+ my ( $type, $msg, $vars ) = @_;
+ return Search::Elasticsearch::Error->new( $type, $msg, $vars, 1 );
+}
+
+#===================================
+sub upgrade_error {
+#===================================
+ my ( $error, $vars ) = @_;
+ return
+ ref($error) && $error->isa('Search::Elasticsearch::Error')
+ ? $error
+ : Search::Elasticsearch::Error->new( "Internal", $error, $vars || {},
+ 1 );
+}
+
+#===================================
+sub is_compat {
+#===================================
+ my ( $attr, $one, $two ) = @_;
+ my $role
+ = $one->does('Search::Elasticsearch::Role::Is_Sync')
+ ? 'Search::Elasticsearch::Role::Is_Sync'
+ : 'Search::Elasticsearch::Role::Is_Async';
+
+ return if eval { $two->does($role); };
+ my $class = ref($two) || $two;
+ die "$attr ($class) does not do $role";
+}
+
+1;
+
+# ABSTRACT: A utility class for internal use by Search::Elasticsearch
diff --git a/t/10_Basic/10_load.t b/t/10_Basic/10_load.t
new file mode 100644
index 0000000..0bb2fa2
--- /dev/null
+++ b/t/10_Basic/10_load.t
@@ -0,0 +1,34 @@
+# 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.
+
+use Test::More;
+
+BEGIN { use_ok('Search::Elasticsearch') }
+
+my ( $e, $p, $t );
+
+ok $e = Search::Elasticsearch->new(), "new client";
+ok $e->does('Search::Elasticsearch::Role::Client::Direct'),
+ "client does Search::Elasticsearch::Role::Client::Direct";
+isa_ok $t = $e->transport, 'Search::Elasticsearch::Transport', "transport";
+isa_ok $p = $t->cxn_pool, 'Search::Elasticsearch::CxnPool::Static',
+ "cxn_pool";
+isa_ok $p->cxn_factory, 'Search::Elasticsearch::Cxn::Factory', "cxn_factory";
+isa_ok $e->logger, 'Search::Elasticsearch::Logger::LogAny', "logger";
+
+done_testing;
+
diff --git a/t/10_Basic_Async/10_load.t b/t/10_Basic_Async/10_load.t
new file mode 100644
index 0000000..771de6b
--- /dev/null
+++ b/t/10_Basic_Async/10_load.t
@@ -0,0 +1,36 @@
+# 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.
+
+use Test::More;
+
+BEGIN { use_ok('Search::Elasticsearch::Async') }
+
+my ( $e, $p, $t );
+
+ok $e = Search::Elasticsearch::Async->new(), "new client";
+ok $e->does('Search::Elasticsearch::Role::Client::Direct'),
+ "client does Search::Elasticsearch::Role::Client::Direct";
+
+isa_ok $t = $e->transport, 'Search::Elasticsearch::Transport::Async',
+ "transport";
+isa_ok $p = $t->cxn_pool, 'Search::Elasticsearch::CxnPool::Async::Static',
+ "cxn_pool";
+isa_ok $p->cxn_factory, 'Search::Elasticsearch::Cxn::Factory', "cxn_factory";
+isa_ok $e->logger, 'Search::Elasticsearch::Logger::LogAny', "logger";
+
+done_testing;
+
diff --git a/t/20_Serializer/10_load_cpanel.t b/t/20_Serializer/10_load_cpanel.t
new file mode 100644
index 0000000..1ea9b57
--- /dev/null
+++ b/t/20_Serializer/10_load_cpanel.t
@@ -0,0 +1,32 @@
+# 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.
+
+use Test::More;
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+
+SKIP: {
+ skip 'Cpanel::JSON::XS not installed' => 1
+ unless eval { require Cpanel::JSON::XS; 1 };
+
+ isa_ok $s, "Cpanel::JSON::XS", 'Cpanel';
+}
+
+done_testing;
+
diff --git a/t/20_Serializer/11_load_xs.t b/t/20_Serializer/11_load_xs.t
new file mode 100644
index 0000000..ed963cd
--- /dev/null
+++ b/t/20_Serializer/11_load_xs.t
@@ -0,0 +1,37 @@
+# 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.
+
+use Test::More;
+
+use lib sub {
+ die "No Cpanel" if $_[1] =~ m{Cpanel/JSON/XS.pm$};
+ return undef;
+};
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+
+SKIP: {
+ skip 'JSON::XS not installed' => 1
+ unless eval { require JSON::XS; 1 };
+
+ isa_ok $s, "JSON::XS", 'JSON::XS';
+}
+
+done_testing;
+
diff --git a/t/20_Serializer/12_load_pp.t b/t/20_Serializer/12_load_pp.t
new file mode 100644
index 0000000..ec374de
--- /dev/null
+++ b/t/20_Serializer/12_load_pp.t
@@ -0,0 +1,38 @@
+# 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.
+
+use Test::More;
+
+use lib sub {
+ die "No Cpanel" if $_[1] =~ m{Cpanel/JSON/XS.pm$};
+ die "No JSON::XS" if $_[1] =~ m{JSON/XS.pm$};
+ return undef;
+};
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+
+SKIP: {
+ skip 'JSON::PP not installed' => 1
+ unless eval { require JSON::PP; 1 };
+
+ isa_ok $s, "JSON::PP", 'JSON::PP';
+}
+
+done_testing;
+
diff --git a/t/20_Serializer/13_preload_cpanel.t b/t/20_Serializer/13_preload_cpanel.t
new file mode 100644
index 0000000..ac7945d
--- /dev/null
+++ b/t/20_Serializer/13_preload_cpanel.t
@@ -0,0 +1,34 @@
+# 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.
+
+use Test::More;
+
+BEGIN {
+ eval { require Cpanel::JSON::XS; 1 } or do {
+ plan skip_all => 'Cpanel::JSON::XS not installed';
+ done_testing;
+ exit;
+ }
+}
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+isa_ok $s, "Cpanel::JSON::XS", 'Cpanel';
+
+done_testing;
+
diff --git a/t/20_Serializer/14_preload_xs.t b/t/20_Serializer/14_preload_xs.t
new file mode 100644
index 0000000..88286e3
--- /dev/null
+++ b/t/20_Serializer/14_preload_xs.t
@@ -0,0 +1,34 @@
+# 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.
+
+use Test::More;
+
+BEGIN {
+ eval { require JSON::XS; 1 } or do {
+ plan skip_all => 'JSON::XS not installed';
+ done_testing;
+ exit;
+ }
+}
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+isa_ok $s, "JSON::XS", 'JSON::XS';
+
+done_testing;
+
diff --git a/t/20_Serializer/20_xs_encode_decode.t b/t/20_Serializer/20_xs_encode_decode.t
new file mode 100644
index 0000000..d95cc51
--- /dev/null
+++ b/t/20_Serializer/20_xs_encode_decode.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::XS; 1 } or do {
+ plan skip_all => 'JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::XS';
+do './t/20_Serializer/encode_pretty.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/21_xs_encode_bulk.t b/t/20_Serializer/21_xs_encode_bulk.t
new file mode 100644
index 0000000..f44b543
--- /dev/null
+++ b/t/20_Serializer/21_xs_encode_bulk.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::XS; 1 } or do {
+ plan skip_all => 'JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::XS';
+do './t/20_Serializer/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/22_xs_encode_pretty.t b/t/20_Serializer/22_xs_encode_pretty.t
new file mode 100644
index 0000000..f44b543
--- /dev/null
+++ b/t/20_Serializer/22_xs_encode_pretty.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::XS; 1 } or do {
+ plan skip_all => 'JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::XS';
+do './t/20_Serializer/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/30_cpanel_encode_decode.t b/t/20_Serializer/30_cpanel_encode_decode.t
new file mode 100644
index 0000000..2ebe5e0
--- /dev/null
+++ b/t/20_Serializer/30_cpanel_encode_decode.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require Cpanel::JSON::XS; 1 } or do {
+ plan skip_all => 'Cpanel::JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::Cpanel';
+do './t/20_Serializer/encode_pretty.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/31_cpanel_encode_bulk.t b/t/20_Serializer/31_cpanel_encode_bulk.t
new file mode 100644
index 0000000..c27a1a7
--- /dev/null
+++ b/t/20_Serializer/31_cpanel_encode_bulk.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require Cpanel::JSON::XS; 1 } or do {
+ plan skip_all => 'Cpanel::JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::Cpanel';
+do './t/20_Serializer/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/32_cpanel_encode_pretty.t b/t/20_Serializer/32_cpanel_encode_pretty.t
new file mode 100644
index 0000000..c27a1a7
--- /dev/null
+++ b/t/20_Serializer/32_cpanel_encode_pretty.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require Cpanel::JSON::XS; 1 } or do {
+ plan skip_all => 'Cpanel::JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::Cpanel';
+do './t/20_Serializer/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/40_pp_encode_decode.t b/t/20_Serializer/40_pp_encode_decode.t
new file mode 100644
index 0000000..230ee20
--- /dev/null
+++ b/t/20_Serializer/40_pp_encode_decode.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::PP; 1 } or do {
+ plan skip_all => 'JSON::PP not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::PP';
+do './t/20_Serializer/encode_pretty.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/41_pp_encode_bulk.t b/t/20_Serializer/41_pp_encode_bulk.t
new file mode 100644
index 0000000..5fbfab8
--- /dev/null
+++ b/t/20_Serializer/41_pp_encode_bulk.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::PP; 1 } or do {
+ plan skip_all => 'JSON::PP not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::PP';
+do './t/20_Serializer/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/42_pp_encode_pretty.t b/t/20_Serializer/42_pp_encode_pretty.t
new file mode 100644
index 0000000..5fbfab8
--- /dev/null
+++ b/t/20_Serializer/42_pp_encode_pretty.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::PP; 1 } or do {
+ plan skip_all => 'JSON::PP not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::PP';
+do './t/20_Serializer/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer/encode_bulk.pl b/t/20_Serializer/encode_bulk.pl
new file mode 100644
index 0000000..ab1b1ac
--- /dev/null
+++ b/t/20_Serializer/encode_bulk.pl
@@ -0,0 +1,60 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use Search::Elasticsearch;
+
+our $JSON_BACKEND;
+my $utf8_bytes = "彈性搜索";
+my $utf8_str = $utf8_bytes;
+utf8::decode($utf8_str);
+my $hash = { "foo" => "$utf8_str" };
+my $arr = [ $hash, $hash ];
+my $json_hash = qq({"foo":"$utf8_bytes"});
+my $json_arr = qq($json_hash\n$json_hash\n);
+
+isa_ok my $s
+ = Search::Elasticsearch->new( serializer => $JSON_BACKEND )
+ ->transport->serializer,
+ "Search::Elasticsearch::Serializer::$JSON_BACKEND", 'Serializer';
+
+is $s->encode_bulk(), undef, #
+ 'Enc - No args returns undef';
+is $s->encode_bulk(undef), undef, #
+ 'Enc - Undef returns undef';
+is $s->encode_bulk(''), '', #
+ 'Enc - Empty string returns same';
+is $s->encode_bulk('foo'), 'foo', #
+ 'Enc - String returns same';
+is $s->encode_bulk($utf8_str), $utf8_bytes, #
+ 'Enc - Unicode string returns encoded';
+is $s->encode_bulk($utf8_bytes), $utf8_bytes, #
+ 'Enc - Unicode bytes returns same';
+is $s->encode_bulk($arr), $json_arr, #
+ 'Enc - Array returns JSON';
+is $s->encode_bulk( [ $json_hash, $json_hash ] ), $json_arr, #
+ 'Enc - Array of strings';
+throws_ok { $s->encode_bulk($hash) } qr/must be an array/, #
+ 'Enc - Hash dies';
+throws_ok { $s->encode_bulk( \$utf8_str ) } qr/Serializer/, #
+ 'Enc - scalar ref dies';
+throws_ok { $s->encode_bulk( [ \$utf8_str ] ) } qr/Serializer/, #
+ 'Enc - array of scalar ref dies';
+
+done_testing;
diff --git a/t/20_Serializer/encode_decode.pl b/t/20_Serializer/encode_decode.pl
new file mode 100644
index 0000000..8116f31
--- /dev/null
+++ b/t/20_Serializer/encode_decode.pl
@@ -0,0 +1,62 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use Search::Elasticsearch;
+
+our $JSON_BACKEND;
+my $utf8_bytes = "彈性搜索";
+my $utf8_str = $utf8_bytes;
+utf8::decode($utf8_str);
+my $hash = { "foo" => "$utf8_str" };
+my $arr = [$hash];
+my $json_hash = qq({"foo":"$utf8_bytes"});
+my $json_arr = qq([$json_hash]);
+
+isa_ok my $s
+ = Search::Elasticsearch->new( serializer => $JSON_BACKEND )
+ ->transport->serializer,
+ "Search::Elasticsearch::Serializer::$JSON_BACKEND", 'Serializer';
+
+is $s->mime_type, 'application/json', 'Mime type is JSON';
+
+# encode
+is $s->encode(), undef, 'Enc - No args returns undef';
+is $s->encode(undef), undef, 'Enc - Undef returns undef';
+is $s->encode(''), '', 'Enc - Empty string returns same';
+is $s->encode('foo'), 'foo', 'Enc - String returns same';
+is $s->encode($utf8_str), $utf8_bytes, 'Enc - Unicode string returns encoded';
+is $s->encode($utf8_bytes), $utf8_bytes, 'Enc - Unicode bytes returns same';
+is $s->encode($hash), $json_hash, 'Enc - Hash returns JSON';
+is $s->encode($arr), $json_arr, 'Enc - Array returns JSON';
+throws_ok { $s->encode( \$utf8_str ) } qr/Serializer/,
+ 'Enc - scalar ref dies';
+
+# decode
+is $s->decode(), undef, 'Dec - No args returns undef';
+is $s->decode(undef), undef, 'Dec - Undef returns undef';
+is $s->decode(''), '', 'Dec - Empty string returns same';
+is $s->decode('foo'), 'foo', 'Dec - String returns same';
+is $s->decode($utf8_bytes), $utf8_str, 'Dec - Unicode bytes returns decoded';
+is $s->decode($utf8_str), $utf8_str, 'Dec - Unicode string returns same';
+cmp_deeply $s->decode($json_hash), $hash, 'Dec - JSON returns hash';
+cmp_deeply $s->decode($json_arr), $arr, 'Dec - JSON returns array';
+throws_ok { $s->decode('{') } qr/Serializer/, 'Dec - invalid JSON dies';
+
+done_testing;
diff --git a/t/20_Serializer/encode_pretty.pl b/t/20_Serializer/encode_pretty.pl
new file mode 100644
index 0000000..83b9e85
--- /dev/null
+++ b/t/20_Serializer/encode_pretty.pl
@@ -0,0 +1,69 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use Search::Elasticsearch;
+
+our $JSON_BACKEND;
+my $utf8_bytes = "彈性搜索";
+my $utf8_str = $utf8_bytes;
+utf8::decode($utf8_str);
+my $hash = { "foo" => "$utf8_str" };
+my $arr = [$hash];
+my $json_hash = <<JSON;
+{
+ "foo" : "$utf8_bytes"
+}
+JSON
+
+my $json_arr = <<JSON;
+[
+ {
+ "foo" : "$utf8_bytes"
+ }
+]
+JSON
+
+isa_ok my $s
+ = Search::Elasticsearch->new( serializer => $JSON_BACKEND )
+ ->transport->serializer,
+ "Search::Elasticsearch::Serializer::$JSON_BACKEND", 'Serializer';
+
+# encode
+is_pretty( [], undef, 'Enc - No args returns undef' );
+is_pretty( [undef], undef, 'Enc - Undef returns undef' );
+is_pretty( [''], '', 'Enc - Empty string returns same' );
+is_pretty( ['foo'], 'foo', 'Enc - String returns same' );
+is_pretty( [$utf8_str], $utf8_bytes, 'Enc - Unicode string returns encoded' );
+is_pretty( [$utf8_bytes], $utf8_bytes, 'Enc - Unicode bytes returns same' );
+is_pretty( [$hash], $json_hash, 'Enc - Hash returns JSON' );
+is_pretty( [$arr], $json_arr, 'Enc - Array returns JSON' );
+
+throws_ok { $s->encode_pretty( \$utf8_str ) } qr/Serializer/, #
+ 'Enc - scalar ref dies';
+
+sub is_pretty {
+ my ( $arg, $expect, $desc ) = @_;
+ my $got = $s->encode_pretty(@$arg);
+ defined $got and $got =~ s/^\s+//gm;
+ defined $expect and $expect =~ s/^\s+//gm;
+ is $got, $expect, $desc;
+}
+
+done_testing;
diff --git a/t/20_Serializer_Async/10_load_cpanel.t b/t/20_Serializer_Async/10_load_cpanel.t
new file mode 100644
index 0000000..0fd4dce
--- /dev/null
+++ b/t/20_Serializer_Async/10_load_cpanel.t
@@ -0,0 +1,32 @@
+# 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.
+
+use Test::More;
+
+use Search::Elasticsearch::Async;
+
+my $s = Search::Elasticsearch::Async->new()->transport->serializer->JSON;
+
+SKIP: {
+ skip 'Cpanel::JSON::XS not installed' => 1
+ unless eval { require Cpanel::JSON::XS; 1 };
+
+ isa_ok $s, "Cpanel::JSON::XS", 'Cpanel';
+}
+
+done_testing;
+
diff --git a/t/20_Serializer_Async/11_load_xs.t b/t/20_Serializer_Async/11_load_xs.t
new file mode 100644
index 0000000..ed963cd
--- /dev/null
+++ b/t/20_Serializer_Async/11_load_xs.t
@@ -0,0 +1,37 @@
+# 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.
+
+use Test::More;
+
+use lib sub {
+ die "No Cpanel" if $_[1] =~ m{Cpanel/JSON/XS.pm$};
+ return undef;
+};
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+
+SKIP: {
+ skip 'JSON::XS not installed' => 1
+ unless eval { require JSON::XS; 1 };
+
+ isa_ok $s, "JSON::XS", 'JSON::XS';
+}
+
+done_testing;
+
diff --git a/t/20_Serializer_Async/12_load_pp.t b/t/20_Serializer_Async/12_load_pp.t
new file mode 100644
index 0000000..ec374de
--- /dev/null
+++ b/t/20_Serializer_Async/12_load_pp.t
@@ -0,0 +1,38 @@
+# 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.
+
+use Test::More;
+
+use lib sub {
+ die "No Cpanel" if $_[1] =~ m{Cpanel/JSON/XS.pm$};
+ die "No JSON::XS" if $_[1] =~ m{JSON/XS.pm$};
+ return undef;
+};
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+
+SKIP: {
+ skip 'JSON::PP not installed' => 1
+ unless eval { require JSON::PP; 1 };
+
+ isa_ok $s, "JSON::PP", 'JSON::PP';
+}
+
+done_testing;
+
diff --git a/t/20_Serializer_Async/13_preload_cpanel.t b/t/20_Serializer_Async/13_preload_cpanel.t
new file mode 100644
index 0000000..ac7945d
--- /dev/null
+++ b/t/20_Serializer_Async/13_preload_cpanel.t
@@ -0,0 +1,34 @@
+# 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.
+
+use Test::More;
+
+BEGIN {
+ eval { require Cpanel::JSON::XS; 1 } or do {
+ plan skip_all => 'Cpanel::JSON::XS not installed';
+ done_testing;
+ exit;
+ }
+}
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+isa_ok $s, "Cpanel::JSON::XS", 'Cpanel';
+
+done_testing;
+
diff --git a/t/20_Serializer_Async/14_preload_xs.t b/t/20_Serializer_Async/14_preload_xs.t
new file mode 100644
index 0000000..88286e3
--- /dev/null
+++ b/t/20_Serializer_Async/14_preload_xs.t
@@ -0,0 +1,34 @@
+# 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.
+
+use Test::More;
+
+BEGIN {
+ eval { require JSON::XS; 1 } or do {
+ plan skip_all => 'JSON::XS not installed';
+ done_testing;
+ exit;
+ }
+}
+
+use Search::Elasticsearch;
+
+my $s = Search::Elasticsearch->new()->transport->serializer->JSON;
+isa_ok $s, "JSON::XS", 'JSON::XS';
+
+done_testing;
+
diff --git a/t/20_Serializer_Async/20_xs_encode_decode.t b/t/20_Serializer_Async/20_xs_encode_decode.t
new file mode 100644
index 0000000..7fe8c7b
--- /dev/null
+++ b/t/20_Serializer_Async/20_xs_encode_decode.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::XS; 1 } or do {
+ plan skip_all => 'JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::XS';
+do './t/20_Serializer_Async/encode_pretty.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/21_xs_encode_bulk.t b/t/20_Serializer_Async/21_xs_encode_bulk.t
new file mode 100644
index 0000000..dc30bf2
--- /dev/null
+++ b/t/20_Serializer_Async/21_xs_encode_bulk.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::XS; 1 } or do {
+ plan skip_all => 'JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::XS';
+do './t/20_Serializer_Async/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/22_xs_encode_pretty.t b/t/20_Serializer_Async/22_xs_encode_pretty.t
new file mode 100644
index 0000000..dc30bf2
--- /dev/null
+++ b/t/20_Serializer_Async/22_xs_encode_pretty.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::XS; 1 } or do {
+ plan skip_all => 'JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::XS';
+do './t/20_Serializer_Async/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/30_cpanel_encode_decode.t b/t/20_Serializer_Async/30_cpanel_encode_decode.t
new file mode 100644
index 0000000..145dac8
--- /dev/null
+++ b/t/20_Serializer_Async/30_cpanel_encode_decode.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require Cpanel::JSON::XS; 1 } or do {
+ plan skip_all => 'Cpanel::JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::Cpanel';
+do './t/20_Serializer_Async/encode_pretty.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/31_cpanel_encode_bulk.t b/t/20_Serializer_Async/31_cpanel_encode_bulk.t
new file mode 100644
index 0000000..228c458
--- /dev/null
+++ b/t/20_Serializer_Async/31_cpanel_encode_bulk.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require Cpanel::JSON::XS; 1 } or do {
+ plan skip_all => 'Cpanel::JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::Cpanel';
+do './t/20_Serializer_Async/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/32_cpanel_encode_pretty.t b/t/20_Serializer_Async/32_cpanel_encode_pretty.t
new file mode 100644
index 0000000..228c458
--- /dev/null
+++ b/t/20_Serializer_Async/32_cpanel_encode_pretty.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require Cpanel::JSON::XS; 1 } or do {
+ plan skip_all => 'Cpanel::JSON::XS not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::Cpanel';
+do './t/20_Serializer_Async/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/40_pp_encode_decode.t b/t/20_Serializer_Async/40_pp_encode_decode.t
new file mode 100644
index 0000000..c2b2a83
--- /dev/null
+++ b/t/20_Serializer_Async/40_pp_encode_decode.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::PP; 1 } or do {
+ plan skip_all => 'JSON::PP not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::PP';
+do './t/20_Serializer_Async/encode_pretty.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/41_pp_encode_bulk.t b/t/20_Serializer_Async/41_pp_encode_bulk.t
new file mode 100644
index 0000000..ec2728c
--- /dev/null
+++ b/t/20_Serializer_Async/41_pp_encode_bulk.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::PP; 1 } or do {
+ plan skip_all => 'JSON::PP not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::PP';
+do './t/20_Serializer_Async/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/42_pp_encode_pretty.t b/t/20_Serializer_Async/42_pp_encode_pretty.t
new file mode 100644
index 0000000..ec2728c
--- /dev/null
+++ b/t/20_Serializer_Async/42_pp_encode_pretty.t
@@ -0,0 +1,27 @@
+# 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.
+
+use Test::More;
+
+eval { require JSON::PP; 1 } or do {
+ plan skip_all => 'JSON::PP not installed';
+ done_testing;
+};
+
+our $JSON_BACKEND = 'JSON::PP';
+do './t/20_Serializer_Async/encode_decode.pl' or die( $@ || $! );
+
diff --git a/t/20_Serializer_Async/encode_bulk.pl b/t/20_Serializer_Async/encode_bulk.pl
new file mode 100644
index 0000000..143fc9e
--- /dev/null
+++ b/t/20_Serializer_Async/encode_bulk.pl
@@ -0,0 +1,60 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+
+our $JSON_BACKEND;
+my $utf8_bytes = "彈性搜索";
+my $utf8_str = $utf8_bytes;
+utf8::decode($utf8_str);
+my $hash = { "foo" => "$utf8_str" };
+my $arr = [ $hash, $hash ];
+my $json_hash = qq({"foo":"$utf8_bytes"});
+my $json_arr = qq($json_hash\n$json_hash\n);
+
+isa_ok my $s
+ = Search::Elasticsearch::Async->new( serializer => $JSON_BACKEND )
+ ->transport->serializer,
+ "Search::Elasticsearch::Serializer::$JSON_BACKEND", 'Serializer';
+
+is $s->encode_bulk(), undef, #
+ 'Enc - No args returns undef';
+is $s->encode_bulk(undef), undef, #
+ 'Enc - Undef returns undef';
+is $s->encode_bulk(''), '', #
+ 'Enc - Empty string returns same';
+is $s->encode_bulk('foo'), 'foo', #
+ 'Enc - String returns same';
+is $s->encode_bulk($utf8_str), $utf8_bytes, #
+ 'Enc - Unicode string returns encoded';
+is $s->encode_bulk($utf8_bytes), $utf8_bytes, #
+ 'Enc - Unicode bytes returns same';
+is $s->encode_bulk($arr), $json_arr, #
+ 'Enc - Array returns JSON';
+is $s->encode_bulk( [ $json_hash, $json_hash ] ), $json_arr, #
+ 'Enc - Array of strings';
+throws_ok { $s->encode_bulk($hash) } qr/must be an array/, #
+ 'Enc - Hash dies';
+throws_ok { $s->encode_bulk( \$utf8_str ) } qr/Serializer/, #
+ 'Enc - scalar ref dies';
+throws_ok { $s->encode_bulk( [ \$utf8_str ] ) } qr/Serializer/, #
+ 'Enc - array of scalar ref dies';
+
+done_testing;
diff --git a/t/20_Serializer_Async/encode_decode.pl b/t/20_Serializer_Async/encode_decode.pl
new file mode 100644
index 0000000..c5e57b4
--- /dev/null
+++ b/t/20_Serializer_Async/encode_decode.pl
@@ -0,0 +1,62 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+
+our $JSON_BACKEND;
+my $utf8_bytes = "彈性搜索";
+my $utf8_str = $utf8_bytes;
+utf8::decode($utf8_str);
+my $hash = { "foo" => "$utf8_str" };
+my $arr = [$hash];
+my $json_hash = qq({"foo":"$utf8_bytes"});
+my $json_arr = qq([$json_hash]);
+
+isa_ok my $s
+ = Search::Elasticsearch::Async->new( serializer => $JSON_BACKEND )
+ ->transport->serializer,
+ "Search::Elasticsearch::Serializer::$JSON_BACKEND", 'Serializer';
+
+is $s->mime_type, 'application/json', 'Mime type is JSON';
+
+# encode
+is $s->encode(), undef, 'Enc - No args returns undef';
+is $s->encode(undef), undef, 'Enc - Undef returns undef';
+is $s->encode(''), '', 'Enc - Empty string returns same';
+is $s->encode('foo'), 'foo', 'Enc - String returns same';
+is $s->encode($utf8_str), $utf8_bytes, 'Enc - Unicode string returns encoded';
+is $s->encode($utf8_bytes), $utf8_bytes, 'Enc - Unicode bytes returns same';
+is $s->encode($hash), $json_hash, 'Enc - Hash returns JSON';
+is $s->encode($arr), $json_arr, 'Enc - Array returns JSON';
+throws_ok { $s->encode( \$utf8_str ) } qr/Serializer/,
+ 'Enc - scalar ref dies';
+
+# decode
+is $s->decode(), undef, 'Dec - No args returns undef';
+is $s->decode(undef), undef, 'Dec - Undef returns undef';
+is $s->decode(''), '', 'Dec - Empty string returns same';
+is $s->decode('foo'), 'foo', 'Dec - String returns same';
+is $s->decode($utf8_bytes), $utf8_str, 'Dec - Unicode bytes returns decoded';
+is $s->decode($utf8_str), $utf8_str, 'Dec - Unicode string returns same';
+cmp_deeply $s->decode($json_hash), $hash, 'Dec - JSON returns hash';
+cmp_deeply $s->decode($json_arr), $arr, 'Dec - JSON returns array';
+throws_ok { $s->decode('{') } qr/Serializer/, 'Dec - invalid JSON dies';
+
+done_testing;
diff --git a/t/20_Serializer_Async/encode_pretty.pl b/t/20_Serializer_Async/encode_pretty.pl
new file mode 100644
index 0000000..8271e2c
--- /dev/null
+++ b/t/20_Serializer_Async/encode_pretty.pl
@@ -0,0 +1,69 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+
+our $JSON_BACKEND;
+my $utf8_bytes = "彈性搜索";
+my $utf8_str = $utf8_bytes;
+utf8::decode($utf8_str);
+my $hash = { "foo" => "$utf8_str" };
+my $arr = [$hash];
+my $json_hash = <<JSON;
+{
+ "foo" : "$utf8_bytes"
+}
+JSON
+
+my $json_arr = <<JSON;
+[
+ {
+ "foo" : "$utf8_bytes"
+ }
+]
+JSON
+
+isa_ok my $s
+ = Search::Elasticsearch::Async->new( serializer => $JSON_BACKEND )
+ ->transport->serializer,
+ "Search::Elasticsearch::Serializer::$JSON_BACKEND", 'Serializer';
+
+# encode
+is_pretty( [], undef, 'Enc - No args returns undef' );
+is_pretty( [undef], undef, 'Enc - Undef returns undef' );
+is_pretty( [''], '', 'Enc - Empty string returns same' );
+is_pretty( ['foo'], 'foo', 'Enc - String returns same' );
+is_pretty( [$utf8_str], $utf8_bytes, 'Enc - Unicode string returns encoded' );
+is_pretty( [$utf8_bytes], $utf8_bytes, 'Enc - Unicode bytes returns same' );
+is_pretty( [$hash], $json_hash, 'Enc - Hash returns JSON' );
+is_pretty( [$arr], $json_arr, 'Enc - Array returns JSON' );
+
+throws_ok { $s->encode_pretty( \$utf8_str ) } qr/Serializer/, #
+ 'Enc - scalar ref dies';
+
+sub is_pretty {
+ my ( $arg, $expect, $desc ) = @_;
+ my $got = $s->encode_pretty(@$arg);
+ defined $got and $got =~ s/^\s+//gm;
+ defined $expect and $expect =~ s/^\s+//gm;
+ is $got, $expect, $desc;
+}
+
+done_testing;
diff --git a/t/30_Logger/10_explicit.t b/t/30_Logger/10_explicit.t
new file mode 100644
index 0000000..b5ee632
--- /dev/null
+++ b/t/30_Logger/10_explicit.t
@@ -0,0 +1,62 @@
+# 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.
+
+use Test::More;
+use Search::Elasticsearch;
+use File::Temp;
+my $file = File::Temp->new( EXLOCK => 0 );
+
+# default
+
+isa_ok my $l = Search::Elasticsearch->new->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Default Logger';
+
+is $l->log_as, 'elasticsearch.event', 'Log as';
+is $l->trace_as, 'elasticsearch.trace', 'Trace as';
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::Null',
+ 'Default - Log to NULL';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::Null',
+ 'Default - Trace to NULL';
+
+# stdout/stderr
+
+isa_ok $l
+ = Search::Elasticsearch->new( log_to => 'Stderr', trace_to => 'Stdout' )
+ ->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Std Logger';
+
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::Stderr',
+ 'Std - Log to Stderr';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::Stdout',
+ 'Std - Trace to Stdout';
+
+# file
+
+isa_ok $l = Search::Elasticsearch->new(
+ log_to => [ 'File', $file->filename ],
+ trace_to => [ 'File', $file->filename ]
+ )->logger, 'Search::Elasticsearch::Logger::LogAny',
+ 'File Logger';
+
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::File',
+ 'File - Log to file';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::File',
+ 'File - Trace to file';
+
+done_testing;
diff --git a/t/30_Logger/20_implicit.t b/t/30_Logger/20_implicit.t
new file mode 100644
index 0000000..bd3965c
--- /dev/null
+++ b/t/30_Logger/20_implicit.t
@@ -0,0 +1,50 @@
+# 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.
+
+use Test::More;
+use Search::Elasticsearch;
+
+use Log::Any::Adapter;
+
+Log::Any::Adapter->set( { category => 'elasticsearch.event' }, 'Stdout' );
+Log::Any::Adapter->set( { category => 'elasticsearch.trace' }, 'Stderr' );
+
+# default
+
+isa_ok my $l = Search::Elasticsearch->new->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Default Logger';
+
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::Stdout',
+ 'Default - Log to Stdout';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::Stderr',
+ 'Default - Trace to Stderr';
+
+# override
+
+isa_ok $l
+ = Search::Elasticsearch->new( log_to => 'Stderr', trace_to => 'Stdout' )
+ ->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Override Logger';
+
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::Stderr',
+ 'Override - Log to Stderr';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::Stdout',
+ 'Override - Trace to Stdout';
+
+done_testing;
diff --git a/t/30_Logger/30_log_methods.t b/t/30_Logger/30_log_methods.t
new file mode 100644
index 0000000..09ac0d7
--- /dev/null
+++ b/t/30_Logger/30_log_methods.t
@@ -0,0 +1,73 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+isa_ok my $l = Search::Elasticsearch->new->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Logger';
+
+test_level($_) for qw(debug info warning error critical trace);
+test_throw($_) for qw(error critical);
+
+done_testing;
+
+#===================================
+sub test_level {
+#===================================
+ my $level = shift;
+ my $levelf = $level . 'f';
+ my $is_level = 'is_' . $level;
+
+ # ->debug
+ ( $method, $format ) = ();
+ ok $l->$level("foo"), "$level";
+ is $method, $level, "$level - method";
+ is $format, "foo", "$level - format";
+
+ # ->debugf
+ ( $method, $format ) = ();
+ ok $l->$levelf( "foo %s", "bar" ), "$levelf";
+ is $method, $level, "$levelf - method";
+ is $format, "foo bar", "$levelf - format";
+
+ # ->is_debug
+ ( $method, $format ) = ();
+ ok $l->$is_level(), "$is_level";
+ is $method, $is_level, "$is_level - method";
+ is $format, undef, "$is_level - format";
+}
+
+#===================================
+sub test_throw {
+#===================================
+ my $level = shift;
+ my $throw = 'throw_' . $level;
+ my $re = qr/\[Request\] \*\* Foo/;
+ ( $method, $format ) = ();
+
+ throws_ok { $l->$throw( 'Request', 'Foo', 42 ) } $re, $throw;
+
+ is $@->{vars}, 42, "$throw - vars";
+ is $method, $level, "$throw - method";
+ like $format, $re, "$throw - format";
+
+}
diff --git a/t/30_Logger/40_trace_request.t b/t/30_Logger/40_trace_request.t
new file mode 100644
index 0000000..20d2cae
--- /dev/null
+++ b/t/30_Logger/40_trace_request.t
@@ -0,0 +1,119 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+ok my $e
+ = Search::Elasticsearch->new( nodes => 'https://foo.bar:444/some/path' ),
+ 'Client';
+
+isa_ok my $l = $e->logger, 'Search::Elasticsearch::Logger::LogAny', 'Logger';
+my $c = $e->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+# No body
+
+ok $l->trace_request(
+ $c,
+ { method => 'POST',
+ qs => { foo => 'bar' },
+ serialize => 'std',
+ path => '/xyz'
+ }
+ ),
+ 'No body';
+
+is $format, <<'REQUEST', 'No body - format';
+# Request to: https://foo.bar:444/some/path
+curl -XPOST 'http://localhost:9200/xyz?foo=bar&pretty=true'
+REQUEST
+
+# Std body
+
+ok $l->trace_request(
+ $c,
+ { method => 'POST',
+ qs => { foo => 'bar' },
+ serialize => 'std',
+ path => '/xyz',
+ body => { foo => qq(bar\n'baz) },
+ data => qq({"foo":"bar\n'baz"}),
+ mime_type => 'application/json',
+ }
+ ),
+ 'Body';
+
+is $format, <<'REQUEST', 'Body - format';
+# Request to: https://foo.bar:444/some/path
+curl -H "Content-type: application/json" -XPOST 'http://localhost:9200/xyz?foo=bar&pretty=true' -d '
+{
+ "foo" : "bar\n\u0027baz"
+}
+'
+REQUEST
+
+# Bulk body
+
+ok $l->trace_request(
+ $c,
+ { method => 'POST',
+ qs => { foo => 'bar' },
+ serialize => 'bulk',
+ path => '/xyz',
+ body => [ { foo => qq(bar\n'baz) }, { foo => qq(bar\n'baz) } ],
+ data => qq({"foo":"bar\\n\\u0027baz"}\n{"foo":"bar\\n\\u0027baz"}\n),
+ mime_type => 'application/json',
+ }
+ ),
+ 'Bulk';
+
+is $format, <<'REQUEST', 'Bulk - format';
+# Request to: https://foo.bar:444/some/path
+curl -H "Content-type: application/json" -XPOST 'http://localhost:9200/xyz?foo=bar&pretty=true' -d '
+{"foo":"bar\n\u0027baz"}
+{"foo":"bar\n\u0027baz"}
+'
+REQUEST
+
+# String body
+
+ok $l->trace_request(
+ $c,
+ { method => 'POST',
+ qs => { foo => 'bar' },
+ serialize => 'std',
+ path => '/xyz',
+ body => qq(The quick brown fox\njumped over the lazy dog's basket),
+ mime_type => 'application/json',
+ }
+ ),
+ 'Body string';
+
+is $format, <<'REQUEST', 'Body string - format';
+# Request to: https://foo.bar:444/some/path
+curl -H "Content-type: application/json" -XPOST 'http://localhost:9200/xyz?foo=bar&pretty=true' -d '
+The quick brown fox
+jumped over the lazy dog\u0027s basket'
+REQUEST
+
+done_testing;
+
diff --git a/t/30_Logger/50_trace_response.t b/t/30_Logger/50_trace_response.t
new file mode 100644
index 0000000..84c9551
--- /dev/null
+++ b/t/30_Logger/50_trace_response.t
@@ -0,0 +1,53 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+ok my $e
+ = Search::Elasticsearch->new( nodes => 'https://foo.bar:444/some/path' ),
+ 'Client';
+
+isa_ok my $l = $e->logger, 'Search::Elasticsearch::Logger::LogAny', 'Logger';
+my $c = $e->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+# No body
+
+ok $l->trace_response( $c, 200, undef, 0.123 ), 'No body';
+
+is $format, <<"RESPONSE", 'No body - format';
+# Response: 200, Took: 123 ms
+#\x20
+RESPONSE
+
+# Body
+
+ok $l->trace_response( $c, 200, { foo => 'bar' }, 0.123 ), 'Body';
+is $format, <<'RESPONSE', 'Body - format';
+# Response: 200, Took: 123 ms
+# {
+# "foo" : "bar"
+# }
+RESPONSE
+
+done_testing;
+
diff --git a/t/30_Logger/60_trace_error.t b/t/30_Logger/60_trace_error.t
new file mode 100644
index 0000000..4f58585
--- /dev/null
+++ b/t/30_Logger/60_trace_error.t
@@ -0,0 +1,68 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+ok my $e
+ = Search::Elasticsearch->new( nodes => 'https://foo.bar:444/some/path' ),
+ 'Client';
+
+isa_ok my $l = $e->logger, 'Search::Elasticsearch::Logger::LogAny', 'Logger';
+my $c = $e->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+# No body
+
+ok $l->trace_error(
+ $c,
+ Search::Elasticsearch::Error->new(
+ 'Missing',
+ "Foo missing",
+ { code => 404 }
+ )
+ ),
+ 'No body';
+
+is $format, <<"RESPONSE", 'No body - format';
+# ERROR: Search::Elasticsearch::Error::Missing Foo missing
+#\x20
+RESPONSE
+
+# Body
+
+ok $l->trace_error(
+ $c,
+ Search::Elasticsearch::Error->new(
+ 'Missing', "Foo missing", { code => 404, body => { foo => 'bar' } }
+ )
+ ),
+ 'Body';
+
+is $format, <<"RESPONSE", 'Body - format';
+# ERROR: Search::Elasticsearch::Error::Missing Foo missing
+# {
+# "foo" : "bar"
+# }
+RESPONSE
+
+done_testing;
+
diff --git a/t/30_Logger/70_trace_comment.t b/t/30_Logger/70_trace_comment.t
new file mode 100644
index 0000000..cb6dc9c
--- /dev/null
+++ b/t/30_Logger/70_trace_comment.t
@@ -0,0 +1,42 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+our $format;
+
+ok my $e
+ = Search::Elasticsearch->new( nodes => 'https://foo.bar:444/some/path' ),
+ 'Client';
+
+isa_ok my $l = $e->logger, 'Search::Elasticsearch::Logger::LogAny', 'Logger';
+my $c = $e->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+ok $l->trace_comment("The quick fox\njumped"), 'Comment';
+
+is $format, <<"COMMENT", 'Comment - format';
+# *** The quick fox
+# *** jumped
+COMMENT
+
+done_testing;
+
diff --git a/t/30_Logger/80_deprecation_methods.t b/t/30_Logger/80_deprecation_methods.t
new file mode 100644
index 0000000..d08a1a7
--- /dev/null
+++ b/t/30_Logger/80_deprecation_methods.t
@@ -0,0 +1,33 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+isa_ok my $l = Search::Elasticsearch->new->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Logger';
+
+( $method, $format ) = ();
+ok $l->deprecation( "foo", { foo => 1 } ), "deprecation";
+is $method, "warning", "deprecation - method";
+is $format, "[DEPRECATION] foo - In request: {foo => 1}", "deprecation - format";
+
+done_testing;
diff --git a/t/30_Logger/90_error_json.t b/t/30_Logger/90_error_json.t
new file mode 100644
index 0000000..f0d7bb2
--- /dev/null
+++ b/t/30_Logger/90_error_json.t
@@ -0,0 +1,39 @@
+# 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.
+
+use Test::More tests => 3;
+use Test::Exception;
+use lib 't/lib';
+
+use_ok('Search::Elasticsearch::Error');
+
+eval 'use JSON::PP;';
+SKIP: {
+ skip 'JSON::PP module not installed', 2 if $@;
+ ok( my $es_error = Search::Elasticsearch::Error->new(
+ 'Missing',
+ "Foo missing",
+ { code => 404 }
+ ),
+ 'Create test error'
+ );
+ like(
+ JSON::PP->new->convert_blessed(1)->encode( { eserr => $es_error } ),
+ qr/Foo missing/,
+ 'encode_json',
+ );
+}
diff --git a/t/30_Logger_Async/10_explicit.t b/t/30_Logger_Async/10_explicit.t
new file mode 100644
index 0000000..faf4fe9
--- /dev/null
+++ b/t/30_Logger_Async/10_explicit.t
@@ -0,0 +1,63 @@
+# 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.
+
+use Test::More;
+use Search::Elasticsearch::Async;
+use File::Temp;
+my $file = File::Temp->new( EXLOCK => 0 );
+
+# default
+
+isa_ok my $l = Search::Elasticsearch::Async->new->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Default Logger';
+
+is $l->log_as, 'elasticsearch.event', 'Log as';
+is $l->trace_as, 'elasticsearch.trace', 'Trace as';
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::Null',
+ 'Default - Log to NULL';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::Null',
+ 'Default - Trace to NULL';
+
+# stdout/stderr
+
+isa_ok $l = Search::Elasticsearch::Async->new(
+ log_to => 'Stderr',
+ trace_to => 'Stdout'
+ )->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Std Logger';
+
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::Stderr',
+ 'Std - Log to Stderr';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::Stdout',
+ 'Std - Trace to Stdout';
+
+# file
+
+isa_ok $l = Search::Elasticsearch::Async->new(
+ log_to => [ 'File', $file->filename ],
+ trace_to => [ 'File', $file->filename ]
+ )->logger, 'Search::Elasticsearch::Logger::LogAny',
+ 'File Logger';
+
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::File',
+ 'File - Log to file';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::File',
+ 'File - Trace to file';
+
+done_testing;
diff --git a/t/30_Logger_Async/20_implicit.t b/t/30_Logger_Async/20_implicit.t
new file mode 100644
index 0000000..da5428f
--- /dev/null
+++ b/t/30_Logger_Async/20_implicit.t
@@ -0,0 +1,51 @@
+# 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.
+
+use Test::More;
+use Search::Elasticsearch::Async;
+
+use Log::Any::Adapter;
+
+Log::Any::Adapter->set( { category => 'elasticsearch.event' }, 'Stdout' );
+Log::Any::Adapter->set( { category => 'elasticsearch.trace' }, 'Stderr' );
+
+# default
+
+isa_ok my $l = Search::Elasticsearch::Async->new->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Default Logger';
+
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::Stdout',
+ 'Default - Log to Stdout';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::Stderr',
+ 'Default - Trace to Stderr';
+
+# override
+
+isa_ok $l = Search::Elasticsearch::Async->new(
+ log_to => 'Stderr',
+ trace_to => 'Stdout'
+ )->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Override Logger';
+
+isa_ok $l->log_handle->adapter, 'Log::Any::Adapter::Stderr',
+ 'Override - Log to Stderr';
+isa_ok $l->trace_handle->adapter, 'Log::Any::Adapter::Stdout',
+ 'Override - Trace to Stdout';
+
+done_testing;
diff --git a/t/30_Logger_Async/30_log_methods.t b/t/30_Logger_Async/30_log_methods.t
new file mode 100644
index 0000000..def3c9a
--- /dev/null
+++ b/t/30_Logger_Async/30_log_methods.t
@@ -0,0 +1,73 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+isa_ok my $l = Search::Elasticsearch::Async->new->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Logger';
+
+test_level($_) for qw(debug info warning error critical trace);
+test_throw($_) for qw(error critical);
+
+done_testing;
+
+#===================================
+sub test_level {
+#===================================
+ my $level = shift;
+ my $levelf = $level . 'f';
+ my $is_level = 'is_' . $level;
+
+ # ->debug
+ ( $method, $format ) = ();
+ ok $l->$level("foo"), "$level";
+ is $method, $level, "$level - method";
+ is $format, "foo", "$level - format";
+
+ # ->debugf
+ ( $method, $format ) = ();
+ ok $l->$levelf( "foo %s", "bar" ), "$levelf";
+ is $method, $level, "$levelf - method";
+ is $format, "foo bar", "$levelf - format";
+
+ # ->is_debug
+ ( $method, $format ) = ();
+ ok $l->$is_level(), "$is_level";
+ is $method, $is_level, "$is_level - method";
+ is $format, undef, "$is_level - format";
+}
+
+#===================================
+sub test_throw {
+#===================================
+ my $level = shift;
+ my $throw = 'throw_' . $level;
+ my $re = qr/\[Request\] \*\* Foo/;
+ ( $method, $format ) = ();
+
+ throws_ok { $l->$throw( 'Request', 'Foo', 42 ) } $re, $throw;
+
+ is $@->{vars}, 42, "$throw - vars";
+ is $method, $level, "$throw - method";
+ like $format, $re, "$throw - format";
+
+}
diff --git a/t/30_Logger_Async/40_trace_request.t b/t/30_Logger_Async/40_trace_request.t
new file mode 100644
index 0000000..64423b3
--- /dev/null
+++ b/t/30_Logger_Async/40_trace_request.t
@@ -0,0 +1,119 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+ok my $e
+ = Search::Elasticsearch::Async->new(
+ nodes => 'https://foo.bar:444/some/path' ), 'Client';
+
+isa_ok my $l = $e->logger, 'Search::Elasticsearch::Logger::LogAny', 'Logger';
+my $c = $e->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+# No body
+
+ok $l->trace_request(
+ $c,
+ { method => 'POST',
+ qs => { foo => 'bar' },
+ serialize => 'std',
+ path => '/xyz'
+ }
+ ),
+ 'No body';
+
+is $format, <<'REQUEST', 'No body - format';
+# Request to: https://foo.bar:444/some/path
+curl -XPOST 'http://localhost:9200/xyz?foo=bar&pretty=true'
+REQUEST
+
+# Std body
+
+ok $l->trace_request(
+ $c,
+ { method => 'POST',
+ qs => { foo => 'bar' },
+ serialize => 'std',
+ path => '/xyz',
+ body => { foo => qq(bar\n'baz) },
+ data => qq({"foo":"bar\n'baz"}),
+ mime_type => 'application/json',
+ }
+ ),
+ 'Body';
+
+is $format, <<'REQUEST', 'Body - format';
+# Request to: https://foo.bar:444/some/path
+curl -H "Content-type: application/json" -XPOST 'http://localhost:9200/xyz?foo=bar&pretty=true' -d '
+{
+ "foo" : "bar\n\u0027baz"
+}
+'
+REQUEST
+
+# Bulk body
+
+ok $l->trace_request(
+ $c,
+ { method => 'POST',
+ qs => { foo => 'bar' },
+ serialize => 'bulk',
+ path => '/xyz',
+ body => [ { foo => qq(bar\n'baz) }, { foo => qq(bar\n'baz) } ],
+ data => qq({"foo":"bar\\n\\u0027baz"}\n{"foo":"bar\\n\\u0027baz"}\n),
+ mime_type => 'application/json',
+ }
+ ),
+ 'Bulk';
+
+is $format, <<'REQUEST', 'Bulk - format';
+# Request to: https://foo.bar:444/some/path
+curl -H "Content-type: application/json" -XPOST 'http://localhost:9200/xyz?foo=bar&pretty=true' -d '
+{"foo":"bar\n\u0027baz"}
+{"foo":"bar\n\u0027baz"}
+'
+REQUEST
+
+# String body
+
+ok $l->trace_request(
+ $c,
+ { method => 'POST',
+ qs => { foo => 'bar' },
+ serialize => 'std',
+ path => '/xyz',
+ body => qq(The quick brown fox\njumped over the lazy dog's basket),
+ mime_type => 'application/json',
+ }
+ ),
+ 'Body string';
+
+is $format, <<'REQUEST', 'Body string - format';
+# Request to: https://foo.bar:444/some/path
+curl -H "Content-type: application/json" -XPOST 'http://localhost:9200/xyz?foo=bar&pretty=true' -d '
+The quick brown fox
+jumped over the lazy dog\u0027s basket'
+REQUEST
+
+done_testing;
+
diff --git a/t/30_Logger_Async/50_trace_response.t b/t/30_Logger_Async/50_trace_response.t
new file mode 100644
index 0000000..e78ce1d
--- /dev/null
+++ b/t/30_Logger_Async/50_trace_response.t
@@ -0,0 +1,54 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+ok my $e
+ = Search::Elasticsearch::Async->new(
+ nodes => 'https://foo.bar:444/some/path' ),
+ 'Client';
+
+isa_ok my $l = $e->logger, 'Search::Elasticsearch::Logger::LogAny', 'Logger';
+my $c = $e->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+# No body
+
+ok $l->trace_response( $c, 200, undef, 0.123 ), 'No body';
+
+is $format, <<"RESPONSE", 'No body - format';
+# Response: 200, Took: 123 ms
+#\x20
+RESPONSE
+
+# Body
+
+ok $l->trace_response( $c, 200, { foo => 'bar' }, 0.123 ), 'Body';
+is $format, <<'RESPONSE', 'Body - format';
+# Response: 200, Took: 123 ms
+# {
+# "foo" : "bar"
+# }
+RESPONSE
+
+done_testing;
+
diff --git a/t/30_Logger_Async/60_trace_error.t b/t/30_Logger_Async/60_trace_error.t
new file mode 100644
index 0000000..dcf6ecd
--- /dev/null
+++ b/t/30_Logger_Async/60_trace_error.t
@@ -0,0 +1,69 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+ok my $e
+ = Search::Elasticsearch::Async->new(
+ nodes => 'https://foo.bar:444/some/path' ),
+ 'Client';
+
+isa_ok my $l = $e->logger, 'Search::Elasticsearch::Logger::LogAny', 'Logger';
+my $c = $e->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+# No body
+
+ok $l->trace_error(
+ $c,
+ Search::Elasticsearch::Error->new(
+ 'Missing',
+ "Foo missing",
+ { code => 404 }
+ )
+ ),
+ 'No body';
+
+is $format, <<"RESPONSE", 'No body - format';
+# ERROR: Search::Elasticsearch::Error::Missing Foo missing
+#\x20
+RESPONSE
+
+# Body
+
+ok $l->trace_error(
+ $c,
+ Search::Elasticsearch::Error->new(
+ 'Missing', "Foo missing", { code => 404, body => { foo => 'bar' } }
+ )
+ ),
+ 'Body';
+
+is $format, <<"RESPONSE", 'Body - format';
+# ERROR: Search::Elasticsearch::Error::Missing Foo missing
+# {
+# "foo" : "bar"
+# }
+RESPONSE
+
+done_testing;
+
diff --git a/t/30_Logger_Async/70_trace_comment.t b/t/30_Logger_Async/70_trace_comment.t
new file mode 100644
index 0000000..313513f
--- /dev/null
+++ b/t/30_Logger_Async/70_trace_comment.t
@@ -0,0 +1,43 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+our $format;
+
+ok my $e
+ = Search::Elasticsearch::Async->new(
+ nodes => 'https://foo.bar:444/some/path' ),
+ 'Client';
+
+isa_ok my $l = $e->logger, 'Search::Elasticsearch::Logger::LogAny', 'Logger';
+my $c = $e->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+ok $l->trace_comment("The quick fox\njumped"), 'Comment';
+
+is $format, <<"COMMENT", 'Comment - format';
+# *** The quick fox
+# *** jumped
+COMMENT
+
+done_testing;
+
diff --git a/t/30_Logger_Async/80_deprecation_methods.t b/t/30_Logger_Async/80_deprecation_methods.t
new file mode 100644
index 0000000..d08a1a7
--- /dev/null
+++ b/t/30_Logger_Async/80_deprecation_methods.t
@@ -0,0 +1,33 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+
+do './t/lib/LogCallback.pl' or die( $@ || $! );
+
+isa_ok my $l = Search::Elasticsearch->new->logger,
+ 'Search::Elasticsearch::Logger::LogAny',
+ 'Logger';
+
+( $method, $format ) = ();
+ok $l->deprecation( "foo", { foo => 1 } ), "deprecation";
+is $method, "warning", "deprecation - method";
+is $format, "[DEPRECATION] foo - In request: {foo => 1}", "deprecation - format";
+
+done_testing;
diff --git a/t/40_Transport/10_tidy_request.t b/t/40_Transport/10_tidy_request.t
new file mode 100644
index 0000000..3c325d5
--- /dev/null
+++ b/t/40_Transport/10_tidy_request.t
@@ -0,0 +1,93 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Search::Elasticsearch;
+
+isa_ok my $t = Search::Elasticsearch->new->transport,
+ 'Search::Elasticsearch::Transport';
+test_tidy( 'Empty', {}, {} );
+test_tidy( 'Method', { method => 'POST' }, { method => 'POST' } );
+test_tidy( 'Path', { path => '/foo' }, { path => '/foo' } );
+test_tidy( 'QS', { qs => { foo => 'bar' } }, { qs => { foo => 'bar' } } );
+
+test_tidy(
+ 'Body - Str',
+ { body => 'foo' },
+ { body => 'foo',
+ data => 'foo',
+ serialize => 'std',
+ mime_type => 'application/json',
+ }
+);
+
+test_tidy(
+ 'Body - Hash',
+ { body => { foo => 'bar' } },
+ { body => { foo => 'bar' },
+ data => '{"foo":"bar"}',
+ serialize => 'std',
+ mime_type => 'application/json',
+ }
+);
+
+test_tidy(
+ 'Body - Array',
+ { body => [ { foo => 'bar' } ] },
+ { body => [ { foo => 'bar' } ],
+ data => '[{"foo":"bar"}]',
+ serialize => 'std',
+ mime_type => 'application/json',
+ }
+);
+
+test_tidy(
+ 'Body - Bulk',
+ { body => [ { foo => 'bar' } ], serialize => 'bulk' },
+ { body => [ { foo => 'bar' } ],
+ data => qq({"foo":"bar"}\n),
+ serialize => 'bulk',
+ mime_type => 'application/json',
+ }
+);
+
+test_tidy(
+ 'MimeType',
+ { mime_type => 'text/plain', body => 'foo' },
+ { mime_type => 'text/plain',
+ body => 'foo',
+ data => 'foo',
+ serialize => 'std'
+ }
+);
+
+#===================================
+sub test_tidy {
+#===================================
+ my ( $title, $params, $test ) = @_;
+ $test = {
+ method => 'GET',
+ path => '/',
+ qs => {},
+ ignore => [],
+ %$test
+ };
+ cmp_deeply $t->tidy_request($params), $test, $title;
+}
+
+done_testing;
diff --git a/t/40_Transport/20_send_body_as.t b/t/40_Transport/20_send_body_as.t
new file mode 100644
index 0000000..67ce46d
--- /dev/null
+++ b/t/40_Transport/20_send_body_as.t
@@ -0,0 +1,77 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Search::Elasticsearch;
+
+my $t = Search::Elasticsearch->new( send_get_body_as => 'GET' )->transport;
+
+test_tidy( 'GET-empty', { path => '/_search' }, {} );
+test_tidy(
+ 'GET-body',
+ { path => '/_search', body => { foo => 'bar' } },
+ { body => { foo => 'bar' },
+ data => '{"foo":"bar"}',
+ method => 'GET',
+ mime_type => 'application/json',
+ serialize => 'std',
+ }
+);
+
+$t = Search::Elasticsearch->new( send_get_body_as => 'POST' )->transport;
+
+test_tidy( 'POST-empty', { path => '/_search' }, {} );
+test_tidy(
+ 'POST-eody',
+ { path => '/_search', body => { foo => 'bar' } },
+ { body => { foo => 'bar' },
+ data => '{"foo":"bar"}',
+ method => 'POST',
+ mime_type => 'application/json',
+ serialize => 'std',
+ }
+);
+
+$t = Search::Elasticsearch->new( send_get_body_as => 'source' )->transport;
+
+test_tidy( 'source-empty', { path => '/_search' }, {} );
+test_tidy(
+ 'source-body',
+ { path => '/_search', body => { foo => 'bar' } },
+ { method => 'GET',
+ qs => { source => '{"foo":"bar"}' },
+ mime_type => 'application/json',
+ serialize => 'std',
+ }
+);
+
+#===================================
+sub test_tidy {
+#===================================
+ my ( $title, $params, $test ) = @_;
+ $test = {
+ method => 'GET',
+ path => '/_search',
+ qs => {},
+ ignore => [],
+ %$test
+ };
+ cmp_deeply $t->tidy_request($params), $test, $title;
+}
+
+done_testing;
diff --git a/t/40_Transport/30_perform_request.t b/t/40_Transport/30_perform_request.t
new file mode 100644
index 0000000..17a78dd
--- /dev/null
+++ b/t/40_Transport/30_perform_request.t
@@ -0,0 +1,86 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+our $t;
+
+# good request
+$t = mock_static_client(
+ { nodes => ['one'] }, #
+ { node => 1, ping => 1 }, #
+ { node => 1, code => '200', content => 1 }
+);
+
+ok $t->perform_request, 'Simple request';
+
+# Request error
+$t = mock_static_client(
+ { nodes => ['one'] },
+ { node => 1, ping => 1 },
+ { node => 1, code => '404', error => 'NotFound' }
+);
+
+throws_ok { $t->perform_request } qr/Missing/, 'Request error';
+
+# Timeout error
+$t = mock_static_client(
+ { nodes => ['one'] },
+ { node => 1, ping => 1 },
+ { node => 1, code => '509', error => 'Timeout' },
+ { node => 1, ping => 1 },
+ { node => 1, code => '200', content => 1 }
+);
+
+throws_ok { $t->perform_request } qr/Timeout/, 'Timeout error';
+ok $t->perform_request, 'Timeout resolved';
+
+# Cxn error
+$t = mock_static_client(
+ { nodes => ['one'] },
+ { node => 1, ping => 1 },
+ { node => 1, code => '509', error => 'Cxn' },
+ { node => 1, ping => 1 },
+ { node => 1, code => '200', content => 1 }
+);
+
+ok $t->perform_request, 'Retried connection error';
+
+# NoNodes from failure
+$t = mock_static_client(
+ { nodes => ['one'] },
+ { node => 1, ping => 1 },
+ { node => 1, code => '509', error => 'Cxn' },
+ { node => 1, ping => 0 },
+);
+
+throws_ok { $t->perform_request } qr/NoNodes/, 'Cxn then bad ping';
+
+# NoNodes reachable
+$t = mock_static_client(
+ { nodes => ['one'] }, #
+ { node => 1, ping => 0 },
+);
+
+throws_ok { $t->perform_request } qr/NoNodes/, 'Initial bad ping';
+
+done_testing;
diff --git a/t/40_Transport_Async/10_tidy_request.t b/t/40_Transport_Async/10_tidy_request.t
new file mode 100644
index 0000000..03da03d
--- /dev/null
+++ b/t/40_Transport_Async/10_tidy_request.t
@@ -0,0 +1,93 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Search::Elasticsearch::Async;
+
+isa_ok my $t = Search::Elasticsearch::Async->new->transport,
+ 'Search::Elasticsearch::Transport::Async';
+test_tidy( 'Empty', {}, {} );
+test_tidy( 'Method', { method => 'POST' }, { method => 'POST' } );
+test_tidy( 'Path', { path => '/foo' }, { path => '/foo' } );
+test_tidy( 'QS', { qs => { foo => 'bar' } }, { qs => { foo => 'bar' } } );
+
+test_tidy(
+ 'Body - Str',
+ { body => 'foo' },
+ { body => 'foo',
+ data => 'foo',
+ serialize => 'std',
+ mime_type => 'application/json',
+ }
+);
+
+test_tidy(
+ 'Body - Hash',
+ { body => { foo => 'bar' } },
+ { body => { foo => 'bar' },
+ data => '{"foo":"bar"}',
+ serialize => 'std',
+ mime_type => 'application/json',
+ }
+);
+
+test_tidy(
+ 'Body - Array',
+ { body => [ { foo => 'bar' } ] },
+ { body => [ { foo => 'bar' } ],
+ data => '[{"foo":"bar"}]',
+ serialize => 'std',
+ mime_type => 'application/json',
+ }
+);
+
+test_tidy(
+ 'Body - Bulk',
+ { body => [ { foo => 'bar' } ], serialize => 'bulk' },
+ { body => [ { foo => 'bar' } ],
+ data => qq({"foo":"bar"}\n),
+ serialize => 'bulk',
+ mime_type => 'application/json',
+ }
+);
+
+test_tidy(
+ 'MimeType',
+ { mime_type => 'text/plain', body => 'foo' },
+ { mime_type => 'text/plain',
+ body => 'foo',
+ data => 'foo',
+ serialize => 'std'
+ }
+);
+
+#===================================
+sub test_tidy {
+#===================================
+ my ( $title, $params, $test ) = @_;
+ $test = {
+ method => 'GET',
+ path => '/',
+ qs => {},
+ ignore => [],
+ %$test
+ };
+ cmp_deeply $t->tidy_request($params), $test, $title;
+}
+
+done_testing;
diff --git a/t/40_Transport_Async/20_send_body_as.t b/t/40_Transport_Async/20_send_body_as.t
new file mode 100644
index 0000000..cc66671
--- /dev/null
+++ b/t/40_Transport_Async/20_send_body_as.t
@@ -0,0 +1,80 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Search::Elasticsearch::Async;
+
+my $t = Search::Elasticsearch::Async->new( send_get_body_as => 'GET' )
+ ->transport;
+
+test_tidy( 'GET-empty', { path => '/_search' }, {} );
+test_tidy(
+ 'GET-body',
+ { path => '/_search', body => { foo => 'bar' } },
+ { body => { foo => 'bar' },
+ data => '{"foo":"bar"}',
+ method => 'GET',
+ mime_type => 'application/json',
+ serialize => 'std',
+ }
+);
+
+$t = Search::Elasticsearch::Async->new( send_get_body_as => 'POST' )
+ ->transport;
+
+test_tidy( 'POST-empty', { path => '/_search' }, {} );
+test_tidy(
+ 'POST-eody',
+ { path => '/_search', body => { foo => 'bar' } },
+ { body => { foo => 'bar' },
+ data => '{"foo":"bar"}',
+ method => 'POST',
+ mime_type => 'application/json',
+ serialize => 'std',
+ }
+);
+
+$t = Search::Elasticsearch::Async->new( send_get_body_as => 'source' )
+ ->transport;
+
+test_tidy( 'source-empty', { path => '/_search' }, {} );
+test_tidy(
+ 'source-body',
+ { path => '/_search', body => { foo => 'bar' } },
+ { method => 'GET',
+ qs => { source => '{"foo":"bar"}' },
+ mime_type => 'application/json',
+ serialize => 'std',
+ }
+);
+
+#===================================
+sub test_tidy {
+#===================================
+ my ( $title, $params, $test ) = @_;
+ $test = {
+ method => 'GET',
+ path => '/_search',
+ qs => {},
+ ignore => [],
+ %$test
+ };
+ cmp_deeply $t->tidy_request($params), $test, $title;
+}
+
+done_testing;
diff --git a/t/40_Transport_Async/30_perform_request.t b/t/40_Transport_Async/30_perform_request.t
new file mode 100644
index 0000000..098ef7c
--- /dev/null
+++ b/t/40_Transport_Async/30_perform_request.t
@@ -0,0 +1,86 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+our $t;
+
+# good request
+$t = mock_static_client(
+ { nodes => ['one'] }, #
+ { node => 1, ping => 1 }, #
+ { node => 1, code => '200', content => 1 }
+);
+
+ok $t->perform_sync_request, 'Simple request';
+
+# Request error
+$t = mock_static_client(
+ { nodes => ['one'] },
+ { node => 1, ping => 1 },
+ { node => 1, code => '404', error => 'NotFound' }
+);
+
+throws_ok { $t->perform_sync_request } qr/Missing/, 'Request error';
+
+# Timeout error
+$t = mock_static_client(
+ { nodes => ['one'] },
+ { node => 1, ping => 1 },
+ { node => 1, code => '509', error => 'Timeout' },
+ { node => 1, ping => 1 },
+ { node => 1, code => '200', content => 1 }
+);
+
+throws_ok { $t->perform_sync_request } qr/Timeout/, 'Timeout error';
+ok $t->perform_sync_request, 'Timeout resolved';
+
+# Cxn error
+$t = mock_static_client(
+ { nodes => ['one'] },
+ { node => 1, ping => 1 },
+ { node => 1, code => '509', error => 'Cxn' },
+ { node => 1, ping => 1 },
+ { node => 1, code => '200', content => 1 }
+);
+
+is $t->perform_sync_request, 1, 'Retried connection error';
+
+# NoNodes from failure
+$t = mock_static_client(
+ { nodes => ['one'] },
+ { node => 1, ping => 1 },
+ { node => 1, code => '509', error => 'Cxn' },
+ { node => 1, ping => 0 },
+);
+
+throws_ok { $t->perform_sync_request } qr/NoNodes/, 'Cxn then bad ping';
+
+# NoNodes reachable
+$t = mock_static_client(
+ { nodes => ['one'] }, #
+ { node => 1, ping => 0 },
+);
+
+throws_ok { $t->perform_sync_request } qr/NoNodes/, 'Initial bad ping';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/10_static_normal.t b/t/50_Cxn_Pool/10_static_normal.t
new file mode 100644
index 0000000..6707f67
--- /dev/null
+++ b/t/50_Cxn_Pool/10_static_normal.t
@@ -0,0 +1,44 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+## Both nodes respond - check ping before first use
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'Ping before first use';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/11_static_node_missing.t b/t/50_Cxn_Pool/11_static_node_missing.t
new file mode 100644
index 0000000..d70bb8e
--- /dev/null
+++ b/t/50_Cxn_Pool/11_static_node_missing.t
@@ -0,0 +1,51 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+## One node missing at first, then joins later
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 0 },
+ { node => 1, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+
+ # force ping on missing node
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+);
+
+ok $t->perform_request && $t->perform_request && $t->perform_request,
+ 'One node missing';
+
+# force ping on missing node
+$t->cxn_pool->cxns->[1]->next_ping(-1);
+
+ok $t->perform_request && $t->perform_request && $t->perform_request,
+ 'Missing node joined - 2';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/12_static_node_fails.t b/t/50_Cxn_Pool/12_static_node_fails.t
new file mode 100644
index 0000000..b1c3414
--- /dev/null
+++ b/t/50_Cxn_Pool/12_static_node_fails.t
@@ -0,0 +1,58 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+## One node fails with a Cxn error, then rejoins
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Cxn' },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+
+ # force ping on missing node
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+);
+
+ok $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'One node throws Cxn';
+
+# force ping on missing node
+$t->cxn_pool->cxns->[0]->next_ping(-1);
+
+ok $t->perform_request && $t->perform_request && $t->perform_request,
+ 'Failed node recovers';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/13_static_node_timesout.t b/t/50_Cxn_Pool/13_static_node_timesout.t
new file mode 100644
index 0000000..ce60770
--- /dev/null
+++ b/t/50_Cxn_Pool/13_static_node_timesout.t
@@ -0,0 +1,49 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+## One node fails with a Timeout error, then rejoins
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Timeout' },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+
+);
+
+ok $t->perform_request
+ && $t->perform_request
+ && !eval { $t->perform_request }
+ && $@ =~ /Timeout/
+ && $t->perform_request,
+ 'One node throws Timeout then recovers';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/14_static_both_nodes_timeout.t b/t/50_Cxn_Pool/14_static_both_nodes_timeout.t
new file mode 100644
index 0000000..3fa2951
--- /dev/null
+++ b/t/50_Cxn_Pool/14_static_both_nodes_timeout.t
@@ -0,0 +1,51 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+## One node fails with a Timeout error and causes good node to timeout
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Timeout' },
+ { node => 2, ping => 1 },
+ { node => 2, code => 509, error => 'Timeout' },
+ { node => 1, ping => 0 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+
+);
+
+ok $t->perform_request
+ && $t->perform_request
+ && !eval { $t->perform_request }
+ && $@ =~ /Timeout/
+ && !eval { $t->perform_request }
+ && $@ =~ /Timeout/
+ && $t->perform_request,
+ 'One node throws Timeout, causing Timeout on other node';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/15_static_both_nodes_fail.t b/t/50_Cxn_Pool/15_static_both_nodes_fail.t
new file mode 100644
index 0000000..61fd77b
--- /dev/null
+++ b/t/50_Cxn_Pool/15_static_both_nodes_fail.t
@@ -0,0 +1,66 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+## All nodes fail
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Cxn' },
+ { node => 2, ping => 0 },
+ { node => 1, ping => 0 },
+
+ # NoNodes
+ { node => 2, ping => 0 },
+ { node => 1, ping => 0 },
+
+ # NoNodes
+ { node => 2, ping => 0 },
+ { node => 1, ping => 0 },
+
+ # NoNodes
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+
+);
+
+ok $t->perform_request
+ && $t->perform_request
+ && !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && $t->perform_request
+ && $t->perform_request,
+ 'Both nodes fails then recover';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/16_static_nodes_starting.t b/t/50_Cxn_Pool/16_static_nodes_starting.t
new file mode 100644
index 0000000..15ecfcd
--- /dev/null
+++ b/t/50_Cxn_Pool/16_static_nodes_starting.t
@@ -0,0 +1,52 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+## Nodes initially unavailable
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 2, ping => 0 },
+ { node => 1, ping => 0 },
+
+ # NoNodes
+ { node => 2, ping => 0 },
+ { node => 1, ping => 0 },
+
+ # NoNodes
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+
+);
+
+ok !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && $t->perform_request
+ && $t->perform_request,
+ 'Nodes initially unavailable';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/17_static_runaway_nodes.t b/t/50_Cxn_Pool/17_static_runaway_nodes.t
new file mode 100644
index 0000000..ed45997
--- /dev/null
+++ b/t/50_Cxn_Pool/17_static_runaway_nodes.t
@@ -0,0 +1,58 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_static_client);
+
+## Runaway nodes (ie wrong HTTP response codes signal node failure, instead of
+## request failure)
+
+my $t = mock_static_client(
+ { nodes => 'one' },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 1, code => 503, error => 'Unavailable' },
+ { node => 1, ping => 1 },
+ { node => 1, code => 503, error => 'Unavailable' },
+
+ # throw Unavailable: too many retries
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 503, error => 'Unavailable' },
+ { node => 1, ping => 1 },
+ { node => 1, code => 503, error => 'Unavailable' },
+
+ # throw Unavailable: too many retries
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+
+);
+
+ok $t->perform_request
+ && !eval { $t->perform_request }
+ && $@ =~ /Unavailable/
+ && !eval { $t->perform_request }
+ && $@ =~ /Unavailable/
+ && $t->perform_request,
+ "Runaway nodes";
+
+done_testing;
diff --git a/t/50_Cxn_Pool/30_sniff_normal.t b/t/50_Cxn_Pool/30_sniff_normal.t
new file mode 100644
index 0000000..392a7f6
--- /dev/null
+++ b/t/50_Cxn_Pool/30_sniff_normal.t
@@ -0,0 +1,42 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Both nodes respond - check ping before first use
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { sniff => [ 'one', 'two' ] },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'Sniff before first use';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/31_sniff_new_nodes.t b/t/50_Cxn_Pool/31_sniff_new_nodes.t
new file mode 100644
index 0000000..f603524
--- /dev/null
+++ b/t/50_Cxn_Pool/31_sniff_new_nodes.t
@@ -0,0 +1,51 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Sniff new nodes
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [], code => 509, error => 'Cxn' },
+ { node => 2, sniff => [ 'two', 'three' ] },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 200, content => 1 },
+
+ # force sniff
+ { node => 3, sniff => [ 'one', 'two', 'three' ] },
+ { node => 5, code => 200, content => 1 },
+ { node => 6, code => 200, content => 1 },
+ { node => 7, code => 200, content => 1 },
+ { node => 5, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'Sniff new nodes';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/32_sniff_node_fails.t b/t/50_Cxn_Pool/32_sniff_node_fails.t
new file mode 100644
index 0000000..8c77cb4
--- /dev/null
+++ b/t/50_Cxn_Pool/32_sniff_node_fails.t
@@ -0,0 +1,50 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Sniff node failures
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 509, error => 'Cxn' },
+ { node => 2, sniff => ['one'] },
+ { node => 4, code => 200, content => 1 },
+ { node => 4, code => 200, content => 1 },
+
+ # force sniff
+ { node => 4, sniff => [ 'one', 'two' ] },
+ { node => 5, code => 200, content => 1 },
+ { node => 6, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->perform_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_request
+ && $t->perform_request,
+ 'Sniff after failure';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/33_sniff_both_nodes_fail.t b/t/50_Cxn_Pool/33_sniff_both_nodes_fail.t
new file mode 100644
index 0000000..4b5192d
--- /dev/null
+++ b/t/50_Cxn_Pool/33_sniff_both_nodes_fail.t
@@ -0,0 +1,60 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Sniff all nodes fail
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 509, error => 'Cxn' },
+ { node => 2, sniff => [], error => 'Cxn', code => 509 },
+ { node => 3, sniff => [], error => 'Cxn', code => 509 },
+ { node => 4, sniff => [], error => 'Cxn', code => 509 },
+ { node => 5, sniff => [], error => 'Cxn', code => 509 },
+
+ # throws NoNodes
+
+ { node => 2, sniff => [], error => 'Cxn', code => 509 },
+ { node => 3, sniff => [], error => 'Cxn', code => 509 },
+ { node => 6, sniff => [], error => 'Cxn', code => 509 },
+ { node => 7, sniff => [], error => 'Cxn', code => 509 },
+
+ # throws NoNodes
+
+ { node => 2, sniff => [ 'one', 'two' ] },
+ { node => 8, code => 200, content => 1 },
+ { node => 9, code => 200, content => 1 },
+ { node => 8, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && $t->perform_request,
+ 'Sniff after all nodes fail';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/34_sniff_node_timeout.t b/t/50_Cxn_Pool/34_sniff_node_timeout.t
new file mode 100644
index 0000000..300b732
--- /dev/null
+++ b/t/50_Cxn_Pool/34_sniff_node_timeout.t
@@ -0,0 +1,55 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Sniff after Timeout error
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 509, error => 'Timeout' },
+
+ # throws Timeout
+
+ { node => 2, sniff => ['one'] },
+ { node => 4, code => 200, content => 1 },
+ { node => 4, code => 200, content => 1 },
+
+ # force sniff
+ { node => 4, sniff => [ 'one', 'two' ] },
+ { node => 5, code => 200, content => 1 },
+ { node => 6, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && !eval { $t->perform_request }
+ && $@ =~ /Timeout/
+ && $t->perform_request
+ && $t->perform_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_request
+ && $t->perform_request,
+ 'Sniff after timeout';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/35_sniff_both_nodes_timeout.t b/t/50_Cxn_Pool/35_sniff_both_nodes_timeout.t
new file mode 100644
index 0000000..dc2caef
--- /dev/null
+++ b/t/50_Cxn_Pool/35_sniff_both_nodes_timeout.t
@@ -0,0 +1,60 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Sniff when bad node timesout causing good node to timeout too
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 509, error => 'Timeout' },
+
+ # throws Timeout
+
+ { node => 2, sniff => ['one'] },
+ { node => 4, code => 509, error => 'Timeout' },
+
+ # throws Timeout
+
+ { node => 4, sniff => ['one'] },
+ { node => 5, code => 200, content => 1 },
+
+ # force sniff
+ { node => 5, sniff => [ 'one', 'two' ] },
+ { node => 6, code => 200, content => 1 },
+ { node => 7, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && !eval { $t->perform_request }
+ && $@ =~ /Timeout/
+ && !eval { $t->perform_request }
+ && $@ =~ /Timeout/
+ && $t->perform_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_request
+ && $t->perform_request,
+ 'Sniff after both nodes timeout';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/36_sniff_nodes_starting.t b/t/50_Cxn_Pool/36_sniff_nodes_starting.t
new file mode 100644
index 0000000..356a9ea
--- /dev/null
+++ b/t/50_Cxn_Pool/36_sniff_nodes_starting.t
@@ -0,0 +1,61 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Nodes initially unavailable
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [], error => 'Cxn', code => 509 },
+ { node => 2, sniff => [], error => 'Cxn', code => 509 },
+
+ # NoNodes
+
+ { node => 3, sniff => [], error => 'Cxn', code => 509 },
+ { node => 4, sniff => [], error => 'Cxn', code => 509 },
+
+ # NoNodes
+
+ { node => 5, sniff => ['one'] },
+ { node => 6, code => 200, content => 1 },
+ { node => 6, code => 200, content => 1 },
+
+ # force sniff
+ { node => 6, sniff => [ 'one', 'two' ] },
+ { node => 7, code => 200, content => 1 },
+ { node => 8, code => 200, content => 1 },
+
+);
+
+ok !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_request }
+ && $@ =~ /NoNodes/
+ && $t->perform_request
+ && $t->perform_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_request
+ && $t->perform_request,
+ 'Sniff unavailable nodes while starting up';
+
+done_testing;
diff --git a/t/50_Cxn_Pool/37_sniff_runaway_nodes.t b/t/50_Cxn_Pool/37_sniff_runaway_nodes.t
new file mode 100644
index 0000000..0bea598
--- /dev/null
+++ b/t/50_Cxn_Pool/37_sniff_runaway_nodes.t
@@ -0,0 +1,57 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Runaway nodes (ie wrong HTTP response codes signal node failure, instead of
+## request failure)
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 503, error => 'Unavailable' },
+ { node => 2, sniff => [ 'one', 'two' ] },
+ { node => 4, code => 503, error => 'Unavailable' },
+
+ # throw Unavailable: too many retries
+
+ { node => 5, sniff => [ 'one', 'two' ] },
+ { node => 6, code => 503, error => 'Unavailable' },
+ { node => 7, sniff => [ 'one', 'two' ] },
+ { node => 8, code => 503, error => 'Unavailable' },
+
+ # throw Unavailable: too many retries
+
+ { node => 9, sniff => [ 'one', 'two' ] },
+ { node => 10, code => 200, content => 1 },
+);
+
+ok $t->perform_request
+ && !eval { $t->perform_request }
+ && $@ =~ /Unavailable/
+ && !eval { $t->perform_request }
+ && $@ =~ /Unavailable/
+ && $t->perform_request,
+ "Runaway nodes";
+
+done_testing;
diff --git a/t/50_Cxn_Pool/38_bad_sniff.t b/t/50_Cxn_Pool/38_bad_sniff.t
new file mode 100644
index 0000000..cd0132f
--- /dev/null
+++ b/t/50_Cxn_Pool/38_bad_sniff.t
@@ -0,0 +1,37 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## For whatever reason, sniffing returns bad data
+
+my $t = mock_sniff_client(
+ { nodes => ['one'] },
+ { node => 1, code => 200, content => '{"nodes":{"one":{}}}' },
+
+ # throw NoNodes
+);
+
+ok !eval { $t->perform_request }
+ && $@ =~ /NoNodes/,
+ "Missing http_address";
+
+done_testing;
diff --git a/t/50_Cxn_Pool/39_sniff_max_content.t b/t/50_Cxn_Pool/39_sniff_max_content.t
new file mode 100644
index 0000000..08ee57d
--- /dev/null
+++ b/t/50_Cxn_Pool/39_sniff_max_content.t
@@ -0,0 +1,68 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_sniff_client);
+
+## Dynamic max content length
+
+my $response = <<RESPONSE;
+{
+ "nodes": {
+ "one": {
+ "http_address": "inet[/one]",
+ "http": {
+ "max_content_length_in_bytes": 200
+ }
+ },
+ "two": {
+ "http_address": "inet[/two]",
+ "http": {
+ "max_content_length_in_bytes": 509
+ }
+ },
+ "three": {
+ "http_address": "inet[/two]"
+ }
+ }
+}
+RESPONSE
+
+my $t = mock_sniff_client(
+ { nodes => ['one'] },
+ { node => 1, code => 200, content => $response },
+ { node => 2, code => 200, content => 1 },
+);
+
+is $t->perform_request
+ && $t->cxn_pool->next_cxn->max_content_length, 200,
+ "Dynamic max content length";
+
+$t = mock_sniff_client(
+ { nodes => ['one'], max_content_length => 1000 },
+ { node => 1, code => 200, content => $response },
+ { node => 2, code => 200, content => 1 },
+);
+
+is $t->perform_request
+ && $t->cxn_pool->next_cxn->max_content_length, 1000,
+ "Dynamic max content length";
+
+done_testing;
diff --git a/t/50_Cxn_Pool/40_sniff_extract_host.t b/t/50_Cxn_Pool/40_sniff_extract_host.t
new file mode 100644
index 0000000..686b9e0
--- /dev/null
+++ b/t/50_Cxn_Pool/40_sniff_extract_host.t
@@ -0,0 +1,38 @@
+# 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.
+
+use Test::More;
+use Search::Elasticsearch;
+
+my $pool
+ = Search::Elasticsearch->new( cxn_pool => 'Sniff' )->transport->cxn_pool;
+
+is $pool->_extract_host('127.0.0.1:9200'), '127.0.0.1:9200', "IP";
+
+is $pool->_extract_host('myhost/127.0.0.1:9200'), '127.0.0.1:9200', "Host/IP";
+
+is $pool->_extract_host('inet[127.0.0.1:9200]'), '127.0.0.1:9200', "inet[IP]";
+
+is $pool->_extract_host('inet[myhost/127.0.0.1:9200]'), '127.0.0.1:9200',
+ "inet[Host/IP]";
+
+is $pool->_extract_host('inet[/127.0.0.1:9200]'), '127.0.0.1:9200',
+ "inet[/IP]";
+
+ok !$pool->_extract_host(), "Undefined";
+
+done_testing;
diff --git a/t/50_Cxn_Pool/50_noping_normal.t b/t/50_Cxn_Pool/50_noping_normal.t
new file mode 100644
index 0000000..b294996
--- /dev/null
+++ b/t/50_Cxn_Pool/50_noping_normal.t
@@ -0,0 +1,46 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_noping_client);
+
+## All nodes respond
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'Round robin';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/51_noping_node_fails.t b/t/50_Cxn_Pool/51_noping_node_fails.t
new file mode 100644
index 0000000..9ce6d9a
--- /dev/null
+++ b/t/50_Cxn_Pool/51_noping_node_fails.t
@@ -0,0 +1,52 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_noping_client);
+
+## Node fails and recover
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 509, error => 'Cxn' },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+ # force check
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->cxn_pool->cxns->[1]->force_ping
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'Node fails and recovers';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/52_noping_node_timesout.t b/t/50_Cxn_Pool/52_noping_node_timesout.t
new file mode 100644
index 0000000..f28f733
--- /dev/null
+++ b/t/50_Cxn_Pool/52_noping_node_timesout.t
@@ -0,0 +1,54 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_noping_client);
+
+## Nodes fail and recover
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 509, error => 'Timeout' },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+ # force check
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && !eval { $t->perform_request }
+ && $@ =~ /Timeout/
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->cxn_pool->cxns->[1]->force_ping
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'Node timesout and recovers';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/53_noping_all_nodes_fail.t b/t/50_Cxn_Pool/53_noping_all_nodes_fail.t
new file mode 100644
index 0000000..4eec050
--- /dev/null
+++ b/t/50_Cxn_Pool/53_noping_all_nodes_fail.t
@@ -0,0 +1,59 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_noping_client);
+
+## All nodes fail and recover
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 509, error => 'Cxn' },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Cxn' },
+ { node => 3, code => 509, error => 'Cxn' },
+ { node => 2, code => 200, content => 1 },
+
+ # force check
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->cxn_pool->cxns->[0]->force_ping
+ && $t->cxn_pool->cxns->[2]->force_ping
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'All nodes fail and recover';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/54_noping_nodes_starting.t b/t/50_Cxn_Pool/54_noping_nodes_starting.t
new file mode 100644
index 0000000..a83d5e9
--- /dev/null
+++ b/t/50_Cxn_Pool/54_noping_nodes_starting.t
@@ -0,0 +1,50 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_noping_client);
+
+## Nodes initially unavailable
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 509, error => 'Cxn' },
+ { node => 2, code => 509, error => 'Cxn' },
+ { node => 3, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+ # force check
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->cxn_pool->cxns->[0]->force_ping
+ && $t->cxn_pool->cxns->[1]->force_ping
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'Nodes starting';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/55_noping_runaway_nodes.t b/t/50_Cxn_Pool/55_noping_runaway_nodes.t
new file mode 100644
index 0000000..749774c
--- /dev/null
+++ b/t/50_Cxn_Pool/55_noping_runaway_nodes.t
@@ -0,0 +1,56 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_noping_client);
+
+## Runaway nodes
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Unavailable' },
+ { node => 2, code => 509, error => 'Unavailable' },
+ { node => 3, code => 509, error => 'Unavailable' },
+
+ # throws unavailable
+ { node => 1, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->perform_request
+ && !eval { $t->perform_request }
+ && $@ =~ /Unavailable/
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request
+ && $t->perform_request,
+ 'Runaway nodes';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool/56_max_retries.t b/t/50_Cxn_Pool/56_max_retries.t
new file mode 100644
index 0000000..bfe6c34
--- /dev/null
+++ b/t/50_Cxn_Pool/56_max_retries.t
@@ -0,0 +1,51 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch;
+use lib 't/lib';
+use MockCxn qw(mock_noping_client);
+
+## Max retries
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ], max_retries => 1 },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Unavailable' },
+ { node => 2, code => 509, error => 'Unavailable' },
+
+ # throws unavailable
+ { node => 3, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+);
+
+ok $t->perform_request()
+ && $t->perform_request
+ && $t->perform_request
+ && !eval { $t->perform_request }
+ && $@ =~ /Unavailable/
+ && $t->perform_request
+ && $t->perform_request,
+ 'Max retries';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/10_static_normal.t b/t/50_Cxn_Pool_Async/10_static_normal.t
new file mode 100644
index 0000000..daa2ae3
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/10_static_normal.t
@@ -0,0 +1,44 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+## Both nodes respond - check ping before first use
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Ping before first use';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/11_static_node_missing.t b/t/50_Cxn_Pool_Async/11_static_node_missing.t
new file mode 100644
index 0000000..52d3642
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/11_static_node_missing.t
@@ -0,0 +1,55 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+## One node missing at first, then joins later
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 0 },
+ { node => 1, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+
+ # force ping on missing node
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'One node missing';
+
+# force ping on missing node
+$t->cxn_pool->cxns->[1]->next_ping(-1);
+
+ok $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Missing node joined - 2';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/12_static_node_fails.t b/t/50_Cxn_Pool_Async/12_static_node_fails.t
new file mode 100644
index 0000000..95c1e0f
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/12_static_node_fails.t
@@ -0,0 +1,60 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+## One node fails with a Cxn error, then rejoins
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Cxn' },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+
+ # force ping on missing node
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'One node throws Cxn';
+
+# force ping on missing node
+$t->cxn_pool->cxns->[0]->next_ping(-1);
+
+ok $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Failed node recovers';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/13_static_node_timesout.t b/t/50_Cxn_Pool_Async/13_static_node_timesout.t
new file mode 100644
index 0000000..c865172
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/13_static_node_timesout.t
@@ -0,0 +1,49 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+## One node fails with a Timeout error, then rejoins
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Timeout' },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+
+);
+
+ok $t->perform_sync_request
+ && $t->perform_sync_request
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Timeout/
+ && $t->perform_sync_request,
+ 'One node throws Timeout then recovers';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/14_static_both_nodes_timeout.t b/t/50_Cxn_Pool_Async/14_static_both_nodes_timeout.t
new file mode 100644
index 0000000..6a201c3
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/14_static_both_nodes_timeout.t
@@ -0,0 +1,51 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+## One node fails with a Timeout error and causes good node to timeout
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Timeout' },
+ { node => 2, ping => 1 },
+ { node => 2, code => 509, error => 'Timeout' },
+ { node => 1, ping => 0 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+
+);
+
+ok $t->perform_sync_request
+ && $t->perform_sync_request
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Timeout/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Timeout/
+ && $t->perform_sync_request,
+ 'One node throws Timeout, causing Timeout on other node';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/15_static_both_nodes_fail.t b/t/50_Cxn_Pool_Async/15_static_both_nodes_fail.t
new file mode 100644
index 0000000..8467d9d
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/15_static_both_nodes_fail.t
@@ -0,0 +1,66 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+## All nodes fail
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Cxn' },
+ { node => 2, ping => 0 },
+ { node => 1, ping => 0 },
+
+ # NoNodes
+ { node => 1, ping => 0 },
+ { node => 2, ping => 0 },
+
+ # NoNodes
+ { node => 1, ping => 0 },
+ { node => 2, ping => 0 },
+
+ # NoNodes
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+
+);
+
+ok $t->perform_sync_request
+ && $t->perform_sync_request
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Both nodes fails then recover';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/16_static_nodes_starting.t b/t/50_Cxn_Pool_Async/16_static_nodes_starting.t
new file mode 100644
index 0000000..0d9a6e1
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/16_static_nodes_starting.t
@@ -0,0 +1,52 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+## Nodes initially unavailable
+
+my $t = mock_static_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 2, ping => 0 },
+ { node => 1, ping => 0 },
+
+ # NoNodes
+ { node => 2, ping => 0 },
+ { node => 1, ping => 0 },
+
+ # NoNodes
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, ping => 1 },
+ { node => 2, code => 200, content => 1 },
+
+);
+
+ok !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Nodes initially unavailable';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/17_static_runaway_nodes.t b/t/50_Cxn_Pool_Async/17_static_runaway_nodes.t
new file mode 100644
index 0000000..225af3f
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/17_static_runaway_nodes.t
@@ -0,0 +1,58 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_static_client);
+
+## Runaway nodes (ie wrong HTTP response codes signal node failure, instead of
+## request failure)
+
+my $t = mock_static_client(
+ { nodes => 'one' },
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 1, code => 503, error => 'Unavailable' },
+ { node => 1, ping => 1 },
+ { node => 1, code => 503, error => 'Unavailable' },
+
+ # throw Unavailable: too many retries
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 503, error => 'Unavailable' },
+ { node => 1, ping => 1 },
+ { node => 1, code => 503, error => 'Unavailable' },
+
+ # throw Unavailable: too many retries
+
+ { node => 1, ping => 1 },
+ { node => 1, code => 200, content => 1 },
+
+);
+
+ok $t->perform_sync_request
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Unavailable/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Unavailable/
+ && $t->perform_sync_request,
+ "Runaway nodes";
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/30_sniff_normal.t b/t/50_Cxn_Pool_Async/30_sniff_normal.t
new file mode 100644
index 0000000..9158e74
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/30_sniff_normal.t
@@ -0,0 +1,43 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Both nodes respond - check ping before first use
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { sniff => [ 'one', 'two' ] },
+ { sniff => [ 'one', 'two' ] },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Sniff before first use';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/31_sniff_new_nodes.t b/t/50_Cxn_Pool_Async/31_sniff_new_nodes.t
new file mode 100644
index 0000000..9e92e17
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/31_sniff_new_nodes.t
@@ -0,0 +1,52 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Sniff new nodes
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [], code => 509, error => 'Cxn' },
+ { node => 2, sniff => [ 'two', 'three' ] },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 200, content => 1 },
+
+ # force sniff
+ { node => 3, sniff => [ 'one', 'two', 'three' ] },
+ { node => 4, sniff => [ 'one', 'two', 'three' ] },
+ { node => 5, code => 200, content => 1 },
+ { node => 6, code => 200, content => 1 },
+ { node => 7, code => 200, content => 1 },
+ { node => 5, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Sniff new nodes';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/32_sniff_node_fails.t b/t/50_Cxn_Pool_Async/32_sniff_node_fails.t
new file mode 100644
index 0000000..1ba2cd4
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/32_sniff_node_fails.t
@@ -0,0 +1,52 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Sniff node failures
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, sniff => [ 'one', 'two' ] },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 509, error => 'Cxn' },
+ { node => 3, sniff => ['one'] },
+ { node => 4, sniff => [] },
+ { node => 5, code => 200, content => 1 },
+ { node => 5, code => 200, content => 1 },
+
+ # force sniff
+ { node => 5, sniff => [ 'one', 'two' ] },
+ { node => 6, code => 200, content => 1 },
+ { node => 7, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Sniff after failure';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/33_sniff_both_nodes_fail.t b/t/50_Cxn_Pool_Async/33_sniff_both_nodes_fail.t
new file mode 100644
index 0000000..ccfde85
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/33_sniff_both_nodes_fail.t
@@ -0,0 +1,62 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Sniff all nodes fail
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, sniff => [ 'one', 'two' ] },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 509, error => 'Cxn' },
+ { node => 3, sniff => [], error => 'Cxn', code => 509 },
+ { node => 4, sniff => [], error => 'Cxn', code => 509 },
+ { node => 5, sniff => [], error => 'Cxn', code => 509 },
+ { node => 6, sniff => [], error => 'Cxn', code => 509 },
+
+ # throws NoNodes
+
+ { node => 3, sniff => [], error => 'Cxn', code => 509 },
+ { node => 4, sniff => [], error => 'Cxn', code => 509 },
+ { node => 7, sniff => [], error => 'Cxn', code => 509 },
+ { node => 8, sniff => [], error => 'Cxn', code => 509 },
+
+ # throws NoNodes
+
+ { node => 3, sniff => [ 'one', 'two' ] },
+ { node => 4, sniff => [ 'one', 'two' ] },
+ { node => 9, code => 200, content => 1 },
+ { node => 10, code => 200, content => 1 },
+ { node => 9, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && $t->perform_sync_request,
+ 'Sniff after all nodes fail';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/34_sniff_node_timeout.t b/t/50_Cxn_Pool_Async/34_sniff_node_timeout.t
new file mode 100644
index 0000000..4065bda
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/34_sniff_node_timeout.t
@@ -0,0 +1,57 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Sniff after Timeout error
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, sniff => [ 'one', 'two' ] },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 509, error => 'Timeout' },
+
+ # throws Timeout
+
+ { node => 3, sniff => ['one'] },
+ { node => 4, sniff => ['one'] },
+ { node => 5, code => 200, content => 1 },
+ { node => 5, code => 200, content => 1 },
+
+ # force sniff
+ { node => 5, sniff => [ 'one', 'two' ] },
+ { node => 6, code => 200, content => 1 },
+ { node => 7, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Timeout/
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Sniff after timeout';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/35_sniff_both_nodes_timeout.t b/t/50_Cxn_Pool_Async/35_sniff_both_nodes_timeout.t
new file mode 100644
index 0000000..72a62fb
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/35_sniff_both_nodes_timeout.t
@@ -0,0 +1,62 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Sniff when bad node timesout causing good node to timeout too
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, sniff => [ 'one', 'two' ] },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 509, error => 'Timeout' },
+
+ # throws Timeout
+
+ { node => 3, sniff => ['one'] },
+ { node => 4, sniff => ['one'] },
+ { node => 5, code => 509, error => 'Timeout' },
+
+ # throws Timeout
+
+ { node => 5, sniff => ['one'] },
+ { node => 6, code => 200, content => 1 },
+
+ # force sniff
+ { node => 6, sniff => [ 'one', 'two' ] },
+ { node => 7, code => 200, content => 1 },
+ { node => 8, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Timeout/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Timeout/
+ && $t->perform_sync_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Sniff after both nodes timeout';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/36_sniff_nodes_starting.t b/t/50_Cxn_Pool_Async/36_sniff_nodes_starting.t
new file mode 100644
index 0000000..818e183
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/36_sniff_nodes_starting.t
@@ -0,0 +1,62 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Nodes initially unavailable
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [], error => 'Cxn', code => 509 },
+ { node => 2, sniff => [], error => 'Cxn', code => 509 },
+
+ # NoNodes
+
+ { node => 3, sniff => [], error => 'Cxn', code => 509 },
+ { node => 4, sniff => [], error => 'Cxn', code => 509 },
+
+ # NoNodes
+
+ { node => 5, sniff => ['one'] },
+ { node => 6, sniff => ['one'] },
+ { node => 7, code => 200, content => 1 },
+ { node => 7, code => 200, content => 1 },
+
+ # force sniff
+ { node => 7, sniff => [ 'one', 'two' ] },
+ { node => 8, code => 200, content => 1 },
+ { node => 9, code => 200, content => 1 },
+
+);
+
+ok !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->cxn_pool->schedule_check
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Sniff unavailable nodes while starting up';
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/37_sniff_runaway_nodes.t b/t/50_Cxn_Pool_Async/37_sniff_runaway_nodes.t
new file mode 100644
index 0000000..ba9139b
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/37_sniff_runaway_nodes.t
@@ -0,0 +1,62 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Runaway nodes (ie wrong HTTP response codes signal node failure, instead of
+## request failure)
+
+my $t = mock_sniff_client(
+ { nodes => [ 'one', 'two' ] },
+
+ { node => 1, sniff => [ 'one', 'two' ] },
+ { node => 2, sniff => [ 'one', 'two' ] },
+ { node => 3, code => 200, content => 1 },
+ { node => 4, code => 503, error => 'Unavailable' },
+ { node => 3, sniff => [ 'one', 'two' ] },
+ { node => 4, sniff => [ 'one', 'two' ] },
+ { node => 5, code => 503, error => 'Unavailable' },
+
+ # throw Unavailable: too many retries
+
+ { node => 6, sniff => [ 'one', 'two' ] },
+ { node => 5, sniff => [ 'one', 'two' ] },
+ { node => 7, code => 503, error => 'Unavailable' },
+ { node => 8, sniff => [ 'one', 'two' ] },
+ { node => 7, sniff => [ 'one', 'two' ] },
+ { node => 9, code => 503, error => 'Unavailable' },
+
+ # throw Unavailable: too many retries
+
+ { node => 10, sniff => [ 'one', 'two' ] },
+ { node => 9, sniff => [ 'one', 'two' ] },
+ { node => 11, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Unavailable/
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Unavailable/
+ && $t->perform_sync_request,
+ "Runaway nodes";
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/38_bad_sniff.t b/t/50_Cxn_Pool_Async/38_bad_sniff.t
new file mode 100644
index 0000000..92d428d
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/38_bad_sniff.t
@@ -0,0 +1,36 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## For whatever reason, sniffing returns bad data
+
+my $t = mock_sniff_client(
+ { nodes => ['one'] },
+ { node => 1, code => 200, content => '{"nodes":{"one":{}}}' },
+
+ # throw NoNodes
+);
+
+ok !eval { $t->perform_sync_request }
+ && $@ =~ /NoNodes/,
+ "Missing http_address";
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/39_sniff_max_content.t b/t/50_Cxn_Pool_Async/39_sniff_max_content.t
new file mode 100644
index 0000000..5c3872f
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/39_sniff_max_content.t
@@ -0,0 +1,74 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_sniff_client);
+
+## Dynamic max content length
+
+my $response = <<RESPONSE;
+{
+ "nodes": {
+ "one": {
+ "http_address": "inet[/one]",
+ "http": {
+ "max_content_length_in_bytes": 200
+ }
+ },
+ "two": {
+ "http_address": "inet[/two]",
+ "http": {
+ "max_content_length_in_bytes": 509
+ }
+ },
+ "three": {
+ "http_address": "inet[/two]"
+ }
+ }
+}
+RESPONSE
+
+my $t = mock_sniff_client(
+ { nodes => ['one'] },
+ { node => 1, code => 200, content => $response },
+ { node => 2, code => 200, content => 1 },
+);
+
+$t->perform_sync_request
+ && $t->cxn_pool->next_cxn->then(
+ sub {
+ is shift()->max_content_length, 200, "Dynamic max content length";
+ }
+ );
+
+$t = mock_sniff_client(
+ { nodes => ['one'], max_content_length => 1000 },
+ { node => 1, code => 200, content => $response },
+ { node => 2, code => 200, content => 1 },
+);
+
+$t->perform_sync_request
+ && $t->cxn_pool->next_cxn->then(
+ sub {
+ is shift()->max_content_length, 1000, "Dynamic max content length";
+ }
+ );
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/40_sniff_extract_host.t b/t/50_Cxn_Pool_Async/40_sniff_extract_host.t
new file mode 100644
index 0000000..71829f9
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/40_sniff_extract_host.t
@@ -0,0 +1,39 @@
+# 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.
+
+use Test::More;
+use Search::Elasticsearch::Async;
+
+my $pool
+ = Search::Elasticsearch::Async->new( cxn_pool => 'Async::Sniff' )
+ ->transport->cxn_pool;
+
+is $pool->_extract_host('127.0.0.1:9200'), '127.0.0.1:9200', "IP";
+
+is $pool->_extract_host('myhost/127.0.0.1:9200'), '127.0.0.1:9200', "Host/IP";
+
+is $pool->_extract_host('inet[127.0.0.1:9200]'), '127.0.0.1:9200', "inet[IP]";
+
+is $pool->_extract_host('inet[myhost/127.0.0.1:9200]'), '127.0.0.1:9200',
+ "inet[Host/IP]";
+
+is $pool->_extract_host('inet[/127.0.0.1:9200]'), '127.0.0.1:9200',
+ "inet[/IP]";
+
+ok !$pool->_extract_host(), "Undefined";
+
+done_testing;
diff --git a/t/50_Cxn_Pool_Async/50_noping_normal.t b/t/50_Cxn_Pool_Async/50_noping_normal.t
new file mode 100644
index 0000000..04837db
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/50_noping_normal.t
@@ -0,0 +1,46 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_noping_client);
+
+## All nodes respond
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Round robin';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/51_noping_node_fails.t b/t/50_Cxn_Pool_Async/51_noping_node_fails.t
new file mode 100644
index 0000000..17195fc
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/51_noping_node_fails.t
@@ -0,0 +1,52 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_noping_client);
+
+## Node fails and recover
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 509, error => 'Cxn' },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+ # force check
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->cxn_pool->cxns->[1]->force_ping
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Node fails and recovers';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/52_noping_node_timesout.t b/t/50_Cxn_Pool_Async/52_noping_node_timesout.t
new file mode 100644
index 0000000..17c9b5c
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/52_noping_node_timesout.t
@@ -0,0 +1,54 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_noping_client);
+
+## Nodes fail and recover
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 509, error => 'Timeout' },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+ # force check
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Timeout/
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->cxn_pool->cxns->[1]->force_ping
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Node timesout and recovers';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/53_noping_all_nodes_fail.t b/t/50_Cxn_Pool_Async/53_noping_all_nodes_fail.t
new file mode 100644
index 0000000..abc2f87
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/53_noping_all_nodes_fail.t
@@ -0,0 +1,59 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_noping_client);
+
+## All nodes fail and recover
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 509, error => 'Cxn' },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Cxn' },
+ { node => 3, code => 509, error => 'Cxn' },
+ { node => 2, code => 200, content => 1 },
+
+ # force check
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->cxn_pool->cxns->[0]->force_ping
+ && $t->cxn_pool->cxns->[2]->force_ping
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'All nodes fail and recover';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/54_noping_nodes_starting.t b/t/50_Cxn_Pool_Async/54_noping_nodes_starting.t
new file mode 100644
index 0000000..f342a1c
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/54_noping_nodes_starting.t
@@ -0,0 +1,50 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_noping_client);
+
+## Nodes initially unavailable
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 509, error => 'Cxn' },
+ { node => 2, code => 509, error => 'Cxn' },
+ { node => 3, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+ # force check
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->cxn_pool->cxns->[0]->force_ping
+ && $t->cxn_pool->cxns->[1]->force_ping
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Nodes starting';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/55_noping_runaway_nodes.t b/t/50_Cxn_Pool_Async/55_noping_runaway_nodes.t
new file mode 100644
index 0000000..00f0edc
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/55_noping_runaway_nodes.t
@@ -0,0 +1,56 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_noping_client);
+
+## Runaway nodes
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ] },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Unavailable' },
+ { node => 2, code => 509, error => 'Unavailable' },
+ { node => 3, code => 509, error => 'Unavailable' },
+
+ # throws unavailable
+ { node => 1, code => 200, content => 1 },
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Unavailable/
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Runaway nodes';
+
+done_testing;
+
diff --git a/t/50_Cxn_Pool_Async/56_max_retries.t b/t/50_Cxn_Pool_Async/56_max_retries.t
new file mode 100644
index 0000000..03abf07
--- /dev/null
+++ b/t/50_Cxn_Pool_Async/56_max_retries.t
@@ -0,0 +1,51 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Search::Elasticsearch::Async;
+use lib 't/lib';
+use MockAsyncCxn qw(mock_noping_client);
+
+## Max retries
+
+my $t = mock_noping_client(
+ { nodes => [ 'one', 'two', 'three' ], max_retries => 1 },
+
+ { node => 1, code => 200, content => 1 },
+ { node => 2, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+ { node => 1, code => 509, error => 'Unavailable' },
+ { node => 2, code => 509, error => 'Unavailable' },
+
+ # throws unavailable
+ { node => 3, code => 200, content => 1 },
+ { node => 3, code => 200, content => 1 },
+
+);
+
+ok $t->perform_sync_request()
+ && $t->perform_sync_request
+ && $t->perform_sync_request
+ && !eval { $t->perform_sync_request }
+ && $@ =~ /Unavailable/
+ && $t->perform_sync_request
+ && $t->perform_sync_request,
+ 'Max retries';
+
+done_testing;
+
diff --git a/t/60_Cxn/10_basic.t b/t/60_Cxn/10_basic.t
new file mode 100644
index 0000000..9aaa1ef
--- /dev/null
+++ b/t/60_Cxn/10_basic.t
@@ -0,0 +1,61 @@
+# 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.
+
+use Test::More;
+use Search::Elasticsearch;
+
+my $c = Search::Elasticsearch->new->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+# MARK LIVE
+
+$c->mark_live;
+
+ok $c->is_live, "Cxn is live";
+is $c->ping_failures, 0, "No ping failures";
+is $c->next_ping, 0, "No ping scheduled";
+
+# MARK DEAD
+
+$c->mark_dead;
+
+ok $c->is_dead, "Cxn is dead";
+is $c->ping_failures, 1, "Has ping failure";
+ok $c->next_ping > time(), "Ping scheduled";
+ok $c->next_ping <= time() + $c->dead_timeout, "Dead timeout x 1";
+
+$c->mark_dead;
+ok $c->is_dead, "Cxn still dead";
+is $c->ping_failures, 2, "Has 2 ping failures";
+ok $c->next_ping > time(), "Ping scheduled";
+ok $c->next_ping <= time() + 2 * $c->dead_timeout, "Dead timeout x 2";
+
+$c->mark_dead for 1 .. 100;
+ok $c->is_dead, "Cxn still dead";
+is $c->ping_failures, 102, "Has 102 ping failures";
+ok $c->next_ping > time(), "Ping scheduled";
+ok $c->next_ping <= time() + $c->max_dead_timeout, "Max dead timeout";
+
+# FORCE PING
+
+$c->force_ping;
+ok $c->is_dead, "Cxn is dead after force ping";
+is $c->ping_failures, 0, "Force ping has no ping failures";
+is $c->next_ping, -1, "Next ping scheduled for now";
+
+done_testing;
diff --git a/t/60_Cxn/20_process_response.t b/t/60_Cxn/20_process_response.t
new file mode 100644
index 0000000..dbe4615
--- /dev/null
+++ b/t/60_Cxn/20_process_response.t
@@ -0,0 +1,125 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Test::Deep;
+use Search::Elasticsearch;
+use Search::Elasticsearch::Role::Cxn qw(PRODUCT_CHECK_HEADER PRODUCT_CHECK_VALUE);
+
+our $PRODUCT_CHECK_VALUE = $Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_VALUE;
+our $PRODUCT_CHECK_HEADER = $Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_HEADER;
+
+my $c = Search::Elasticsearch->new->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn'),
+ 'Does Search::Elasticsearch::Role::Cxn';
+
+my ( $code, $response );
+
+### OK GET
+( $code, $response )
+ = $c->process_response( { method => 'GET', ignore => [] },
+ 200, "OK", '{"ok":1}', { 'content-type' => 'application/json', $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE } );
+
+is $code, 200, "OK GET - code";
+cmp_deeply $response, { ok => 1 }, "OK GET - body";
+
+### OK GET - Text body
+( $code, $response )
+ = $c->process_response( { method => 'GET', ignore => [] },
+ 200, "OK", 'Foo', { 'content-type' => 'text/plain', $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE } );
+
+is $code, 200, "OK GET Text body - code";
+cmp_deeply $response, 'Foo', "OK GET Text body - body";
+
+### OK GET - Empty body
+( $code, $response )
+ = $c->process_response( { method => 'GET', ignore => [] },
+ 200, "OK", '', { $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE } );
+
+is $code, 200, "OK GET Empty body - code";
+cmp_deeply $response, '', "OK GET Empty body - body";
+
+### OK HEAD
+( $code, $response )
+ = $c->process_response( { method => 'HEAD', ignore => [] }, 200, "OK", '', { $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE } );
+
+is $code, 200, "OK HEAD - code";
+is $response, 1, "OK HEAD - body";
+
+### Missing GET
+throws_ok {
+ $c->process_response(
+ { method => 'GET', ignore => [] },
+ 404, "Missing",
+ '{"error": "Something is missing"}',
+ { 'content-type' => 'application/json' }
+ );
+}
+qr/Missing/, "Missing GET";
+
+### Missing GET ignore
+( $code, $response ) = $c->process_response(
+ { method => 'GET', ignore => [404] },
+ 404, "Missing",
+ '{"error": "Something is missing"}',
+ { 'content-type' => 'application/json' }
+);
+
+is $code, 404, "Missing GET - code";
+is $response, undef, "Missing GET - body";
+
+### Missing HEAD
+( $code, $response )
+ = $c->process_response( { method => 'HEAD', ignore => [] },
+ 404, "Missing", '', { $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE });
+is $code, 404, "Missing HEAD - code";
+is $response, undef, "Missing HEAD - body";
+
+### Request error
+throws_ok {
+ $c->process_response(
+ { method => 'GET', ignore => [] },
+ 400, "Request",
+ '{"error":"error in body"}',
+ { 'content-type' => 'application/json', $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE }
+ );
+}
+qr/\[400\] error in body/, "Request error";
+
+### Timeout error
+throws_ok {
+ $c->process_response( { method => 'GET', ignore => [] },
+ 509, "28: Timed out,read timeout" );
+}
+qr/Timeout/, "Timeout error";
+
+### Product check without x-elastic-product header throws error
+throws_ok {
+ $c->process_response( { method => 'GET', ignore => [] },
+ 200, "OK" );
+}
+qr/ProductCheck/, "ProductCheck error";
+
+### Product check with x-elastic-product is OK
+( $code, $response )
+ = $c->process_response( { method => 'GET', ignore => [] },
+ 200, "OK", '', { $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE } );
+
+is $code, 200, "OK Product check is present";
+
+done_testing;
diff --git a/t/60_Cxn/30_http.t b/t/60_Cxn/30_http.t
new file mode 100644
index 0000000..b3288a7
--- /dev/null
+++ b/t/60_Cxn/30_http.t
@@ -0,0 +1,267 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Test::Deep;
+use Search::Elasticsearch;
+use Search::Elasticsearch::Role::Cxn;
+use MIME::Base64;
+
+sub is_cxn(@);
+
+my $username = 'ThisIsAVeryLongUsernameAndThatIsOKYouSee';
+my $password = 'CorrectHorseBatteryStapleCorrectHorseBatteryStaple';
+my $userinfo = "$username:$password";
+my $userinfo_b64 = MIME::Base64::encode_base64( $userinfo, "" );
+my $useragent = Search::Elasticsearch::Role::Cxn::get_user_agent();
+my $metaheader = Search::Elasticsearch::Role::Cxn::get_meta_header();
+
+### Scalar nodes ###
+
+is_cxn "Default", new_cxn(), {};
+
+is_cxn "Host",
+ new_cxn( nodes => 'foo' ),
+ { host => 'foo', port => '80', uri => 'http://foo:80' };
+
+is_cxn "Host:Port",
+ new_cxn( nodes => 'foo:1000' ),
+ { host => 'foo', port => '1000', uri => 'http://foo:1000' };
+
+is_cxn "HTTPS", new_cxn( nodes => 'https://foo' ),
+ {
+ scheme => 'https',
+ host => 'foo',
+ port => '443',
+ uri => 'https://foo:443'
+ };
+
+is_cxn "Path",
+ new_cxn( nodes => 'foo/bar' ),
+ { host => 'foo', port => '80', uri => 'http://foo:80/bar' };
+
+is_cxn "Userinfo", new_cxn( nodes => "http://$userinfo\@localhost/" ),
+ {
+ port => '80',
+ uri => 'http://localhost:80',
+ default_headers => {
+ 'Authorization' => "Basic $userinfo_b64",
+ 'User-Agent' => $useragent,
+ 'x-elastic-client-meta' => $metaheader
+ },
+ userinfo => $userinfo
+ };
+
+is_cxn "IPv4",
+ new_cxn( nodes => '127.0.0.1' ),
+ { host => '127.0.0.1', port => '80', uri => 'http://127.0.0.1:80' };
+
+is_cxn "Scheme:IPv4", new_cxn( nodes => 'https://127.0.0.1' ),
+ {
+ host => '127.0.0.1',
+ port => '443',
+ uri => 'https://127.0.0.1:443',
+ scheme => 'https'
+ };
+
+is_cxn "IPv4:Port",
+ new_cxn( nodes => '127.0.0.1:1000' ),
+ { host => '127.0.0.1', port => '1000', uri => 'http://127.0.0.1:1000' };
+
+is_cxn "Scheme:IPv4:Port", new_cxn( nodes => 'https://127.0.0.1:1000' ),
+ {
+ host => '127.0.0.1',
+ port => '1000',
+ uri => 'https://127.0.0.1:1000',
+ scheme => 'https'
+ };
+
+is_cxn "IPv6",
+ new_cxn( nodes => '::1' ),
+ { host => '::1', port => '80', uri => 'http://[::1]:80' };
+
+is_cxn "Scheme:IPv6", new_cxn( nodes => 'https://[::1]' ),
+ {
+ host => '::1',
+ port => '443',
+ uri => 'https://[::1]:443',
+ scheme => 'https'
+ };
+
+is_cxn "IPv6:Port",
+ new_cxn( nodes => '[::1]:1000' ),
+ { host => '::1', port => '1000', uri => 'http://[::1]:1000' };
+
+is_cxn "Scheme:IPv6:Port", new_cxn( nodes => 'https://[::1]:1000' ),
+ {
+ host => '::1',
+ port => '1000',
+ uri => 'https://[::1]:1000',
+ scheme => 'https'
+ };
+
+### Options with scalar ###
+
+is_cxn "HTTPS option", new_cxn( nodes => 'foo', use_https => 1 ),
+ {
+ scheme => 'https',
+ host => 'foo',
+ port => '443',
+ uri => 'https://foo:443'
+ };
+
+is_cxn "HTTPS option with settings",
+ new_cxn( nodes => 'http://foo', use_https => 1 ),
+ { scheme => 'http', host => 'foo', port => '80', uri => 'http://foo:80' };
+
+is_cxn "Port option",
+ new_cxn( nodes => 'foo', port => 456 ),
+ { host => 'foo', port => '456', uri => 'http://foo:456' };
+
+is_cxn "Port option with settings",
+ new_cxn( nodes => 'foo:123', port => 456 ),
+ { host => 'foo', port => '123', uri => 'http://foo:123' };
+
+is_cxn "Path option",
+ new_cxn( nodes => 'foo', path_prefix => '/bar/' ),
+ { host => 'foo', port => 80, uri => 'http://foo:80/bar' };
+
+is_cxn "Path option with settings",
+ new_cxn( nodes => 'foo/baz/', path_prefix => '/bar/' ),
+ { host => 'foo', port => 80, uri => 'http://foo:80/baz' };
+
+is_cxn "Userinfo option", new_cxn( nodes => 'foo', userinfo => $userinfo ),
+ {
+ host => 'foo',
+ port => 80,
+ uri => 'http://foo:80',
+ default_headers => {
+ 'Authorization' => "Basic $userinfo_b64",
+ 'User-Agent' => $useragent,
+ 'x-elastic-client-meta' => $metaheader
+ },
+ userinfo => $userinfo
+ };
+
+is_cxn "Userinfo option with settings",
+
+ # Note that userinfo as specified is explicitly different to that
+ # provided in the nodes string
+ new_cxn( nodes => "$userinfo\@foo", userinfo => 'foo:baz' ),
+ {
+ host => 'foo',
+ port => 80,
+ uri => 'http://foo:80',
+ default_headers => {
+ 'Authorization' => "Basic $userinfo_b64",
+ 'User-Agent' => $useragent,
+ 'x-elastic-client-meta' => $metaheader
+ },
+ userinfo => $userinfo
+ };
+
+is_cxn "Deflate option",
+ new_cxn( deflate => 1 ),
+ { default_headers => { 'Accept-Encoding' => 'deflate', 'User-Agent' => $useragent, 'x-elastic-client-meta' => $metaheader } };
+
+is_cxn "IPv4 with Port",
+ new_cxn( nodes => '127.0.0.1', port => 456 ),
+ { host => '127.0.0.1', port => '456', uri => 'http://127.0.0.1:456' };
+
+is_cxn "IPv6 with Port",
+ new_cxn( nodes => '::1', port => 456 ),
+ { host => '::1', port => '456', uri => 'http://[::1]:456' };
+
+### Hash ###
+is_cxn "Hash host",
+ new_cxn( nodes => { host => 'foo' } ),
+ { host => 'foo', port => 80, uri => 'http://foo:80' };
+
+is_cxn "Hash port",
+ new_cxn( nodes => { port => '123' } ),
+ { port => 123, uri => 'http://localhost:123' };
+
+is_cxn "Hash path",
+ new_cxn( nodes => { path => 'baz' } ),
+ { port => 80, uri => 'http://localhost:80/baz' };
+
+is_cxn "Hash IPv4 host",
+ new_cxn( nodes => { host => '127.0.0.1' } ),
+ { host => '127.0.0.1', port => 80, uri => 'http://127.0.0.1:80' };
+
+is_cxn "Hash IPv6 host",
+ new_cxn( nodes => { host => '::1' } ),
+ { host => '::1', port => 80, uri => 'http://[::1]:80' };
+
+# Build URI
+is new_cxn()->build_uri( { path => '/' } ), 'http://localhost:9200/',
+ "Default URI";
+
+is new_cxn( { nodes => 'http://localhost:9200/foo' } )
+ ->build_uri( { path => '/_search' } ),
+ 'http://localhost:9200/foo/_search',
+ "URI with path";
+
+is new_cxn( { default_qs_params => { session => 'key' } } )
+ ->build_uri( { path => '/_search' } ),
+ 'http://localhost:9200/_search?session=key',
+ "default_qs_params";
+
+my $uri = new_cxn( { default_qs_params => { session => 'key' } } )
+ ->build_uri( { path => '/_search', qs => { foo => 'bar' } } );
+
+like $uri, qr{^http://localhost:9200/_search?}, "default_qs_params and qs - 1";
+like $uri, qr{session=key}, "default_qs_params and qs - 2";
+like $uri, qr{foo=bar}, "default_qs_params and qs - 3";
+
+is new_cxn( { default_qs_params => { session => 'key' } } )
+ ->build_uri( { path => '/_search', qs => { session => 'bar' } } ),
+ 'http://localhost:9200/_search?session=bar',
+ "default_qs_params overwritten";
+
+done_testing;
+
+#===================================
+sub is_cxn (@) {
+#===================================
+ my ( $title, $cxn, $params ) = @_;
+ my %params = (
+ host => 'localhost',
+ port => '9200',
+ scheme => 'http',
+ uri => 'http://localhost:9200',
+ default_headers => {
+ 'User-Agent' => $useragent,
+ 'x-elastic-client-meta' => $metaheader
+ },
+ userinfo => '',
+ %$params
+ );
+
+ for my $key ( sort keys %params ) {
+ my $val = $cxn->$key;
+ $val = "$val" unless ref $val eq 'HASH';
+ cmp_deeply $val, $params{$key}, "$title - $key";
+ }
+}
+
+#===================================
+sub new_cxn {
+#===================================
+ return Search::Elasticsearch->new(@_)->transport->cxn_pool->cxns->[0];
+}
diff --git a/t/60_Cxn_Async/10_basic.t b/t/60_Cxn_Async/10_basic.t
new file mode 100644
index 0000000..d27ba52
--- /dev/null
+++ b/t/60_Cxn_Async/10_basic.t
@@ -0,0 +1,61 @@
+# 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.
+
+use Test::More;
+use Search::Elasticsearch::Async;
+
+my $c = Search::Elasticsearch::Async->new->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn::Async'),
+ 'Does Search::Elasticsearch::Role::Cxn::Async';
+
+# MARK LIVE
+
+$c->mark_live;
+
+ok $c->is_live, "Cxn is live";
+is $c->ping_failures, 0, "No ping failures";
+is $c->next_ping, 0, "No ping scheduled";
+
+# MARK DEAD
+
+$c->mark_dead;
+
+ok $c->is_dead, "Cxn is dead";
+is $c->ping_failures, 1, "Has ping failure";
+ok $c->next_ping > time(), "Ping scheduled";
+ok $c->next_ping <= time() + $c->dead_timeout, "Dead timeout x 1";
+
+$c->mark_dead;
+ok $c->is_dead, "Cxn still dead";
+is $c->ping_failures, 2, "Has 2 ping failures";
+ok $c->next_ping > time(), "Ping scheduled";
+ok $c->next_ping <= time() + 2 * $c->dead_timeout, "Dead timeout x 2";
+
+$c->mark_dead for 1 .. 100;
+ok $c->is_dead, "Cxn still dead";
+is $c->ping_failures, 102, "Has 102 ping failures";
+ok $c->next_ping > time(), "Ping scheduled";
+ok $c->next_ping <= time() + $c->max_dead_timeout, "Max dead timeout";
+
+# FORCE PING
+
+$c->force_ping;
+ok $c->is_dead, "Cxn is dead after force ping";
+is $c->ping_failures, 0, "Force ping has no ping failures";
+is $c->next_ping, -1, "Next ping scheduled for now";
+
+done_testing;
diff --git a/t/60_Cxn_Async/20_process_response.t b/t/60_Cxn_Async/20_process_response.t
new file mode 100644
index 0000000..9288172
--- /dev/null
+++ b/t/60_Cxn_Async/20_process_response.t
@@ -0,0 +1,111 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Test::Deep;
+use Search::Elasticsearch::Async;
+use Search::Elasticsearch::Role::Cxn qw(PRODUCT_CHECK_HEADER PRODUCT_CHECK_VALUE);
+
+our $PRODUCT_CHECK_VALUE = $Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_VALUE;
+our $PRODUCT_CHECK_HEADER = $Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_HEADER;
+
+my $c = Search::Elasticsearch::Async->new->transport->cxn_pool->cxns->[0];
+ok $c->does('Search::Elasticsearch::Role::Cxn::Async'),
+ 'Does Search::Elasticsearch::Role::Cxn::Async';
+
+my ( $code, $response );
+
+### OK GET
+( $code, $response )
+ = $c->process_response( { method => 'GET', ignore => [] },
+ 200, "OK", '{"ok":1}', { 'content-type' => 'application/json', $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE } );
+
+is $code, 200, "OK GET - code";
+cmp_deeply $response, { ok => 1 }, "OK GET - body";
+
+### OK GET - Text body
+( $code, $response )
+ = $c->process_response( { method => 'GET', ignore => [] },
+ 200, "OK", 'Foo', { 'content-type' => 'text/plain', $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE } );
+
+is $code, 200, "OK GET Text body - code";
+cmp_deeply $response, 'Foo', "OK GET Text body - body";
+
+### OK GET - Empty body
+( $code, $response )
+ = $c->process_response( { method => 'GET', ignore => [] }, 200, "OK",
+ '', { $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE } );
+
+is $code, 200, "OK GET Empty body - code";
+cmp_deeply $response, '', "OK GET Empty body - body";
+
+### OK HEAD
+( $code, $response )
+ = $c->process_response( { method => 'HEAD', ignore => [] }, 200, "OK", '', { $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE });
+
+is $code, 200, "OK HEAD - code";
+is $response, 1, "OK HEAD - body";
+
+### Missing GET
+throws_ok {
+ $c->process_response(
+ { method => 'GET', ignore => [] },
+ 404, "Missing",
+ '{"error": "Something is missing"}',
+ { 'content-type' => 'application/json', $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE }
+ );
+}
+qr/Missing/, "Missing GET";
+
+### Missing GET ignore
+( $code, $response ) = $c->process_response(
+ { method => 'GET', ignore => [404] },
+ 404, "Missing",
+ '{"error": "Something is missing"}',
+ { 'content-type' => 'application/json', $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE }
+);
+
+is $code, 404, "Missing GET - code";
+is $response, undef, "Missing GET - body";
+
+### Missing HEAD
+( $code, $response )
+ = $c->process_response( { method => 'HEAD', ignore => [] },
+ 404, "Missing" );
+is $code, 404, "Missing HEAD - code";
+is $response, undef, "Missing HEAD - body";
+
+### Request error
+throws_ok {
+ $c->process_response(
+ { method => 'GET', ignore => [] },
+ 400, "Request",
+ '{"error":"error in body"}',
+ { 'content-type' => 'application/json', $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE }
+ );
+}
+qr/\[400\] error in body/, "Request error";
+
+### Timeout error
+throws_ok {
+ $c->process_response( { method => 'GET', ignore => [] },
+ 509, "28: Timed out,read timeout" );
+}
+qr/Timeout/, "Timeout error";
+
+done_testing;
diff --git a/t/60_Cxn_Async/30_http.t b/t/60_Cxn_Async/30_http.t
new file mode 100644
index 0000000..92c135f
--- /dev/null
+++ b/t/60_Cxn_Async/30_http.t
@@ -0,0 +1,191 @@
+# 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.
+
+use Test::More;
+use Test::Exception;
+use Test::Deep;
+use Search::Elasticsearch::Async;
+use Search::Elasticsearch::Role::Cxn;
+
+sub is_cxn(@);
+
+my $username = 'ThisIsAVeryLongUsernameAndThatIsOKYouSee';
+my $password = 'CorrectHorseBatteryStapleCorrectHorseBatteryStaple';
+my $useragent = Search::Elasticsearch::Role::Cxn::get_user_agent();
+my $metaheader = Search::Elasticsearch::Role::Cxn::get_meta_header();
+
+### Scalar nodes ###
+
+is_cxn "Default", new_cxn(), {};
+
+is_cxn "Host",
+ new_cxn( nodes => 'foo' ),
+ { host => 'foo', port => '80', uri => 'http://foo:80' };
+
+is_cxn "Host:Port",
+ new_cxn( nodes => 'foo:1000' ),
+ { host => 'foo', port => '1000', uri => 'http://foo:1000' };
+
+is_cxn "HTTPS", new_cxn( nodes => 'https://foo' ),
+ {
+ scheme => 'https',
+ host => 'foo',
+ port => '443',
+ uri => 'https://foo:443'
+ };
+
+is_cxn "Path",
+ new_cxn( nodes => 'foo/bar' ),
+ { host => 'foo', port => '80', uri => 'http://foo:80/bar' };
+
+is_cxn "IPv4",
+ new_cxn( nodes => '127.0.0.1' ),
+ { host => '127.0.0.1', port => '80', uri => 'http://127.0.0.1:80' };
+
+is_cxn "Scheme:IPv4", new_cxn( nodes => 'https://127.0.0.1' ),
+ {
+ host => '127.0.0.1',
+ port => '443',
+ uri => 'https://127.0.0.1:443',
+ scheme => 'https'
+ };
+
+is_cxn "IPv4:Port",
+ new_cxn( nodes => '127.0.0.1:1000' ),
+ { host => '127.0.0.1', port => '1000', uri => 'http://127.0.0.1:1000' };
+
+is_cxn "Scheme:IPv4:Port", new_cxn( nodes => 'https://127.0.0.1:1000' ),
+ {
+ host => '127.0.0.1',
+ port => '1000',
+ uri => 'https://127.0.0.1:1000',
+ scheme => 'https'
+ };
+
+### Options with scalar ###
+
+is_cxn "HTTPS option", new_cxn( nodes => 'foo', use_https => 1 ),
+ {
+ scheme => 'https',
+ host => 'foo',
+ port => '443',
+ uri => 'https://foo:443'
+ };
+
+is_cxn "HTTPS option with settings",
+ new_cxn( nodes => 'http://foo', use_https => 1 ),
+ { scheme => 'http', host => 'foo', port => '80', uri => 'http://foo:80' };
+
+is_cxn "Port option",
+ new_cxn( nodes => 'foo', port => 456 ),
+ { host => 'foo', port => '456', uri => 'http://foo:456' };
+
+is_cxn "Port option with settings",
+ new_cxn( nodes => 'foo:123', port => 456 ),
+ { host => 'foo', port => '123', uri => 'http://foo:123' };
+
+is_cxn "Path option",
+ new_cxn( nodes => 'foo', path_prefix => '/bar/' ),
+ { host => 'foo', port => 80, uri => 'http://foo:80/bar' };
+
+is_cxn "Path option with settings",
+ new_cxn( nodes => 'foo/baz/', path_prefix => '/bar/' ),
+ { host => 'foo', port => 80, uri => 'http://foo:80/baz' };
+
+is_cxn "Deflate option",
+ new_cxn( deflate => 1 ),
+ { default_headers => { 'Accept-Encoding' => 'deflate', 'User-Agent' => $useragent, 'x-elastic-client-meta' => $metaheader } };
+
+is_cxn "IPv4 with Port",
+ new_cxn( nodes => '127.0.0.1', port => 456 ),
+ { host => '127.0.0.1', port => '456', uri => 'http://127.0.0.1:456' };
+
+### Hash ###
+is_cxn "Hash host",
+ new_cxn( nodes => { host => 'foo' } ),
+ { host => 'foo', port => 80, uri => 'http://foo:80' };
+
+is_cxn "Hash port",
+ new_cxn( nodes => { port => '123' } ),
+ { port => 123, uri => 'http://localhost:123' };
+
+is_cxn "Hash path",
+ new_cxn( nodes => { path => 'baz' } ),
+ { port => 80, uri => 'http://localhost:80/baz' };
+
+is_cxn "Hash IPv4 host",
+ new_cxn( nodes => { host => '127.0.0.1' } ),
+ { host => '127.0.0.1', port => 80, uri => 'http://127.0.0.1:80' };
+
+# Build URI
+is new_cxn()->build_uri( { path => '/' } ), 'http://localhost:9200/',
+ "Default URI";
+
+is new_cxn( { nodes => 'http://localhost:9200/foo' } )
+ ->build_uri( { path => '/_search' } ),
+ 'http://localhost:9200/foo/_search',
+ "URI with path";
+
+is new_cxn( { default_qs_params => { session => 'key' } } )
+ ->build_uri( { path => '/_search' } ),
+ 'http://localhost:9200/_search?session=key',
+ "default_qs_params";
+
+my $uri = new_cxn( { default_qs_params => { session => 'key' } } )
+ ->build_uri( { path => '/_search', qs => { foo => 'bar' } } );
+
+like $uri, qr{^http://localhost:9200/_search?}, "default_qs_params and qs - 1";
+like $uri, qr{session=key}, "default_qs_params and qs - 2";
+like $uri, qr{foo=bar}, "default_qs_params and qs - 3";
+
+is new_cxn( { default_qs_params => { session => 'key' } } )
+ ->build_uri( { path => '/_search', qs => { session => 'bar' } } ),
+ 'http://localhost:9200/_search?session=bar',
+ "default_qs_params overwritten";
+
+done_testing;
+
+#===================================
+sub is_cxn (@) {
+#===================================
+ my ( $title, $cxn, $params ) = @_;
+ my %params = (
+ host => 'localhost',
+ port => '9200',
+ scheme => 'http',
+ uri => 'http://localhost:9200',
+ default_headers => {
+ 'User-Agent' => $useragent,
+ 'x-elastic-client-meta' => $metaheader
+ },
+ userinfo => '',
+ %$params
+ );
+
+ for my $key ( sort keys %params ) {
+ my $val = $cxn->$key;
+ $val = "$val" unless ref $val eq 'HASH';
+ cmp_deeply $val, $params{$key}, "$title - $key";
+ }
+}
+
+#===================================
+sub new_cxn {
+#===================================
+ return Search::Elasticsearch::Async->new(@_)
+ ->transport->cxn_pool->cxns->[0];
+}
diff --git a/t/95_TestServer/00_test_server.t b/t/95_TestServer/00_test_server.t
new file mode 100644
index 0000000..49c9a1c
--- /dev/null
+++ b/t/95_TestServer/00_test_server.t
@@ -0,0 +1,81 @@
+# 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.
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use File::Temp;
+use POSIX ":sys_wait_h";
+
+use Search::Elasticsearch;
+use Search::Elasticsearch::TestServer;
+
+my @pids;
+
+SKIP: {
+ skip 'ES_HOME not set', 7 unless $ENV{ES_HOME};
+
+ my $tempdir = File::Temp->newdir( 'testserver-XXXXX', DIR => '/tmp' );
+ my $server = Search::Elasticsearch::TestServer->new;
+
+ my $nodes = $server->start();
+
+ ok( $nodes, "server->start returned nodes" )
+ or diag explain { server => $server };
+ ok( defined( $server->pids ), "server->pids defined" );
+ cmp_ok( scalar @{ $server->pids }, '>', 0, "more than 0 pids" );
+ @pids = @{ $server->pids };
+
+ subtest 'ES pids are alive' => sub {
+ verify_pids_alive(@pids);
+ };
+
+ $server->shutdown;
+
+ note 'sleep to give ES time to die';
+ sleep 5;
+
+ subtest 'ES pids are dead after shutdown' => sub {
+ verify_pids_dead(@pids);
+ };
+
+ eval { $server->shutdown };
+ is( $@, '', "second shutdown did not set error" );
+
+ subtest 'ES pids are dead after second shutdown' => sub {
+ verify_pids_dead(@pids);
+ };
+}
+
+done_testing;
+
+#important to waitpid or kill0 will return true for zombies.
+sub verify_pids_alive {
+ for my $pid (@_) {
+ waitpid( $pid, WNOHANG );
+ ok( kill( 0, $pid ), "pid $pid is alive" );
+ }
+}
+
+sub verify_pids_dead {
+ for my $pid (@_) {
+ waitpid( $pid, WNOHANG );
+ ok( !kill( 0, $pid ), "pid $pid is dead" );
+ }
+}
diff --git a/t/95_TestServer/10_test_server_fork.t b/t/95_TestServer/10_test_server_fork.t
new file mode 100644
index 0000000..d68e35a
--- /dev/null
+++ b/t/95_TestServer/10_test_server_fork.t
@@ -0,0 +1,92 @@
+# 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.
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::SharedFork;
+
+use File::Temp;
+use POSIX ":sys_wait_h";
+
+use Search::Elasticsearch;
+use Search::Elasticsearch::TestServer;
+
+my $pids = [];
+
+SKIP: {
+ skip 'ES_HOME not set', 8 unless $ENV{ES_HOME};
+
+ my $tempdir = File::Temp->newdir( 'testserver-XXXXX', DIR => '/tmp' );
+ my $server = Search::Elasticsearch::TestServer->new();
+
+ my $nodes = $server->start();
+
+ ok( $nodes, "server->start returned nodes" )
+ or diag explain { server => $server };
+ ok( defined( $server->pids ), "server->pids defined" );
+ cmp_ok( scalar @{ $server->pids }, '>', 0, "more than 0 pids" );
+ $pids = \@{ $server->pids };
+
+ verify_pids_alive( $pids, 'ES pids are alive' );
+
+ {
+ my $pid = fork;
+ die "cannot fork" unless defined $pid;
+
+ if ( $pid == 0 ) {
+ verify_pids_alive( $pids, 'ES pids are alive in child' );
+ exit 0;
+ }
+ else {
+ verify_pids_alive( $pids, 'ES pids are alive in parent' );
+ waitpid( $pid, 0 );
+ sleep 5;
+ verify_pids_alive( $pids,
+ 'ES pids are alive in parent after child dies' );
+ }
+ }
+
+ $server->shutdown;
+
+ note 'sleep to give ES time to die';
+ sleep 5;
+
+ verify_pids_dead( $pids, 'ES pids are dead after shutdown' );
+
+}
+done_testing;
+
+#important to waitpid or kill0 will return true for zombies.
+sub verify_pids_alive {
+ my ( $pids, $msg ) = @_;
+ $msg = '' if !defined $msg;
+ for my $pid (@$pids) {
+ waitpid( $pid, WNOHANG );
+ ok( kill( 0, $pid ), "$msg: pid $pid is alive" );
+ }
+}
+
+sub verify_pids_dead {
+ my ( $pids, $msg ) = @_;
+ $msg = '' if !defined $msg;
+ for my $pid (@$pids) {
+ waitpid( $pid, WNOHANG );
+ ok( !kill( 0, $pid ), "$msg: pid $pid is dead" );
+ }
+}
diff --git a/t/Client_2_0/00_print_version.t b/t/Client_2_0/00_print_version.t
deleted file mode 100644
index 58e75fa..0000000
--- a/t/Client_2_0/00_print_version.t
+++ /dev/null
@@ -1,23 +0,0 @@
-use Test::More;
-use lib 't/lib';
-$ENV{ES_VERSION} = '2_0';
-my $es = do "es_sync.pl" or die( $@ || $! );
-
-eval {
- my $v = $es->info->{version};
- diag "";
- diag "";
- diag "Testing against Elasticsearch v" . $v->{number};
- for ( sort keys %$v ) {
- diag sprintf "%-20s: %s", $_, $v->{$_};
- }
- diag "";
- diag "Client: " . ref($es);
- diag "Cxn: " . $es->transport->cxn_pool->cxn_factory->cxn_class;
- diag "GET Body: " . $es->transport->send_get_body_as;
- diag "";
- pass "ES Version";
-} or fail "ES Version";
-
-done_testing;
-
diff --git a/t/Client_2_0/10_live.t b/t/Client_2_0/10_live.t
deleted file mode 100644
index 4a9c3c0..0000000
--- a/t/Client_2_0/10_live.t
+++ /dev/null
@@ -1,27 +0,0 @@
-use Test::More;
-use Test::Deep;
-use Test::Exception;
-use strict;
-use warnings;
-use lib 't/lib';
-
-my $es;
-$ENV{ES_VERSION} = '2_0';
-local $ENV{ES_CXN_POOL};
-
-$ENV{ES_CXN_POOL} = 'Static';
-$es = do "es_sync.pl" or die( $@ || $! );
-is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Static';
-
-$ENV{ES_CXN_POOL} = 'Static::NoPing';
-$es = do "es_sync.pl" or die( $@ || $! );
-is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Static::NoPing';
-
-$ENV{ES_CXN_POOL} = 'Sniff';
-$es = do "es_sync.pl" or die( $@ || $! );
-is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Sniff';
-
-my ($node) = values %{ $es->transport->cxn_pool->next_cxn->sniff };
-ok $node->{http}{max_content_length_in_bytes}, 'Sniffs max_content length';
-
-done_testing;
diff --git a/t/Client_2_0/15_conflict.t b/t/Client_2_0/15_conflict.t
deleted file mode 100644
index ae95ac5..0000000
--- a/t/Client_2_0/15_conflict.t
+++ /dev/null
@@ -1,29 +0,0 @@
-use Test::More;
-use strict;
-use warnings;
-use lib 't/lib';
-
-$ENV{ES_VERSION} = '2_0';
-my $es = do "es_sync.pl" or die( $@ || $! );
-
-$es->indices->delete( index => '_all' );
-
-$es->index( index => 'test', type => 'test', id => 1, body => {} );
-
-my $error;
-
-eval {
- $es->index(
- index => 'test',
- type => 'test',
- id => 1,
- body => {},
- version => 2
- );
- 1;
-} or $error = $@;
-
-ok $error->is('Conflict'), 'Conflict Exception';
-is $error->{vars}{current_version}, 1, "Error has current version v1";
-
-done_testing;
diff --git a/t/Client_2_0/20_fork_httptiny.t b/t/Client_2_0/20_fork_httptiny.t
deleted file mode 100644
index 4a3665c..0000000
--- a/t/Client_2_0/20_fork_httptiny.t
+++ /dev/null
@@ -1,6 +0,0 @@
-use lib 't/lib';
-
-$ENV{ES_VERSION} = '2_0';
-$ENV{ES_CXN} = 'HTTPTiny';
-do "es_sync_fork.pl" or die( $@ || $! );
-
diff --git a/t/Client_2_0/21_fork_lwp.t b/t/Client_2_0/21_fork_lwp.t
deleted file mode 100644
index 0a969ba..0000000
--- a/t/Client_2_0/21_fork_lwp.t
+++ /dev/null
@@ -1,6 +0,0 @@
-use lib 't/lib';
-
-$ENV{ES_VERSION} = '2_0';
-$ENV{ES_CXN} = 'LWP';
-do "es_sync_fork.pl" or die( $@ || $! );
-
diff --git a/t/Client_2_0/22_fork_hijk.t b/t/Client_2_0/22_fork_hijk.t
deleted file mode 100644
index 1f004a6..0000000
--- a/t/Client_2_0/22_fork_hijk.t
+++ /dev/null
@@ -1,6 +0,0 @@
-use lib 't/lib';
-
-$ENV{ES_VERSION} = '2_0';
-$ENV{ES_CXN} = 'Hijk';
-do "es_sync_fork.pl" or die( $@ || $! );
-
diff --git a/t/Client_2_0/30_bulk_add_action.t b/t/Client_2_0/30_bulk_add_action.t
deleted file mode 100644
index 226d2e9..0000000
--- a/t/Client_2_0/30_bulk_add_action.t
+++ /dev/null
@@ -1,277 +0,0 @@
-use Test::More;
-use Test::Deep;
-use Test::Exception;
-use strict;
-use warnings;
-use lib 't/lib';
-
-$ENV{ES_VERSION} = '2_0';
-my $es = do "es_sync.pl" or die( $@ || $! );
-my $b = $es->bulk_helper;
-
-$b->_serializer->_set_canonical;
-
-## EMPTY
-
-ok $b->add_action(), 'Empty add action';
-
-## INDEX ACTIONS ##
-
-ok $b->add_action(
- index => {
- index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- timestamp => 1380019061000,
- ttl => '10m',
- version => 1,
- version_type => 'external',
- source => { foo => 'bar' },
- },
- index => {
- _index => 'foo',
- _type => 'bar',
- _id => 2,
- _routing => 2,
- _parent => 2,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- _source => { foo => 'bar' },
- }
- ),
- 'Add index actions';
-
-cmp_deeply $b->_buffer,
- [
- q({"index":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"foo":"bar"}),
- q({"index":{"_id":2,"_index":"foo","_parent":2,"_routing":2,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"foo":"bar"})
- ],
- "Index actions in buffer";
-
-is $b->_buffer_size, 336, "Index actions buffer size";
-is $b->_buffer_count, 2, "Index actions buffer count";
-
-$b->clear_buffer;
-
-## CREATE ACTIONS ##
-
-ok $b->add_action(
- create => {
- index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- timestamp => 1380019061000,
- ttl => '10m',
- version => 1,
- version_type => 'external',
- source => { foo => 'bar' },
- },
- create => {
- _index => 'foo',
- _type => 'bar',
- _id => 2,
- _routing => 2,
- _parent => 2,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- _source => { foo => 'bar' },
- }
- ),
- 'Add create actions';
-
-cmp_deeply $b->_buffer,
- [
- q({"create":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"foo":"bar"}),
- q({"create":{"_id":2,"_index":"foo","_parent":2,"_routing":2,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"foo":"bar"})
- ],
- "Create actions in buffer";
-
-is $b->_buffer_size, 338, "Create actions buffer size";
-is $b->_buffer_count, 2, "Create actions buffer count";
-
-$b->clear_buffer;
-
-## DELETE ACTIONS ##
-
-ok $b->add_action(
- delete => {
- index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- version => 1,
- version_type => 'external',
- },
- delete => {
- _index => 'foo',
- _type => 'bar',
- _id => 2,
- _routing => 2,
- _parent => 2,
- _version => 1,
- _version_type => 'external',
- }
- ),
- 'Add delete actions';
-
-cmp_deeply $b->_buffer,
- [
- q({"delete":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_type":"bar","_version":1,"_version_type":"external"}}),
- q({"delete":{"_id":2,"_index":"foo","_parent":2,"_routing":2,"_type":"bar","_version":1,"_version_type":"external"}}),
- ],
- "Delete actions in buffer";
-
-is $b->_buffer_size, 230, "Delete actions buffer size";
-is $b->_buffer_count, 2, "Delete actions buffer count";
-
-$b->clear_buffer;
-
-## UPDATE ACTIONS ##
-
-ok $b->add_action(
- update => {
- index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- timestamp => 1380019061000,
- ttl => '10m',
- version => 1,
- version_type => 'external',
- doc => { foo => 'bar' },
- doc_as_upsert => 1,
- },
- update => {
- index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- timestamp => 1380019061000,
- ttl => '10m',
- version => 1,
- version_type => 'external',
- upsert => { counter => 0 },
- script => '_ctx.source.counter+=incr',
- lang => 'mvel',
- params => { incr => 1 },
- },
- update => {
- _index => 'foo',
- _type => 'bar',
- _id => 1,
- _routing => 1,
- _parent => 1,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- doc => { foo => 'bar' },
- doc_as_upsert => 1,
- },
- update => {
- _index => 'foo',
- _type => 'bar',
- _id => 1,
- _routing => 1,
- _parent => 1,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- upsert => { counter => 0 },
- script => '_ctx.source.counter+=incr',
- lang => 'mvel',
- params => { incr => 1 },
- },
- update => {
- _index => 'foo',
- _type => 'bar',
- _id => 1,
- _routing => 1,
- _parent => 1,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- doc => { foo => 'bar' },
- doc_as_upsert => 1,
- detect_noop => 1,
- },
- update => {
- _index => 'foo',
- _type => 'bar',
- _id => 1,
- _routing => 1,
- _parent => 1,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- upsert => { counter => 0 },
- script => '_ctx.source.counter+=incr',
- lang => 'mvel',
- params => { incr => 1 },
- detect_noop => 1,
- _retry_on_conflict => 3,
- },
- ),
- 'Add update actions';
-
-cmp_deeply $b->_buffer,
- [
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"doc":{"foo":"bar"},"doc_as_upsert":1}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"lang":"mvel","params":{"incr":1},"script":"_ctx.source.counter+=incr","upsert":{"counter":0}}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"doc":{"foo":"bar"},"doc_as_upsert":1}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"lang":"mvel","params":{"incr":1},"script":"_ctx.source.counter+=incr","upsert":{"counter":0}}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"detect_noop":1,"doc":{"foo":"bar"},"doc_as_upsert":1}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"_retry_on_conflict":3,"detect_noop":1,"lang":"mvel","params":{"incr":1},"script":"_ctx.source.counter+=incr","upsert":{"counter":0}}),
- ],
- "Update actions in buffer";
-
-is $b->_buffer_size, 1393, "Update actions buffer size";
-is $b->_buffer_count, 6, "Update actions buffer count";
-
-$b->clear_buffer;
-
-## ERRORS ##
-throws_ok { $b->add_action( 'foo' => {} ) } qr/Unrecognised action/,
- 'Bad action';
-
-throws_ok { $b->add_action( 'index', 'bar' ) } qr/Missing <params>/,
- 'Missing params';
-
-throws_ok { $b->add_action( index => { type => 't' } ) }
-qr/Missing .*<index>/, 'Missing index';
-throws_ok { $b->add_action( index => { index => 'i' } ) }
-qr/Missing .*<type>/, 'Missing type';
-throws_ok { $b->add_action( index => { index => 'i', type => 't' } ) }
-qr/Missing <source>/, 'Missing source';
-
-throws_ok {
- $b->add_action(
- index => { index => 'i', type => 't', _source => {}, foo => 1 } );
-}
-qr/Unknown params/, 'Unknown params';
-
-done_testing;
diff --git a/t/Client_2_0/31_bulk_helpers.t b/t/Client_2_0/31_bulk_helpers.t
deleted file mode 100644
index 0f60b0a..0000000
--- a/t/Client_2_0/31_bulk_helpers.t
+++ /dev/null
@@ -1,281 +0,0 @@
-use Test::More;
-use Test::Deep;
-use Test::Exception;
-use strict;
-use warnings;
-use lib 't/lib';
-
-$ENV{ES_VERSION} = '2_0';
-my $es = do "es_sync.pl" or die( $@ || $! );
-my $b = $es->bulk_helper(
- index => 'i',
- type => 't'
-);
-my $s = $b->_serializer;
-$s->_set_canonical;
-
-## INDEX ##
-
-ok $b->index(), 'Empty index';
-
-ok $b->index(
- { index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- timestamp => 1380019061000,
- ttl => '10m',
- version => 1,
- version_type => 'external',
- source => { foo => 'bar' },
- },
- { _index => 'foo',
- _type => 'bar',
- _id => 2,
- _routing => 2,
- _parent => 2,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- _source => { foo => 'bar' },
- }
- ),
- 'Index';
-
-cmp_deeply $b->_buffer,
- [
- q({"index":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"foo":"bar"}),
- q({"index":{"_id":2,"_index":"foo","_parent":2,"_routing":2,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"foo":"bar"})
- ],
- "Index in buffer";
-
-is $b->_buffer_size, 336, "Index buffer size";
-is $b->_buffer_count, 2, "Index buffer count";
-
-$b->clear_buffer;
-
-## CREATE ##
-
-ok $b->create(), 'Create empty';
-
-ok $b->create(
- { index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- timestamp => 1380019061000,
- ttl => '10m',
- version => 1,
- version_type => 'external',
- source => { foo => 'bar' },
- },
- { _index => 'foo',
- _type => 'bar',
- _id => 2,
- _routing => 2,
- _parent => 2,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- _source => { foo => 'bar' },
- }
- ),
- 'Create';
-
-cmp_deeply $b->_buffer,
- [
- q({"create":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"foo":"bar"}),
- q({"create":{"_id":2,"_index":"foo","_parent":2,"_routing":2,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"foo":"bar"})
- ],
- "Create in buffer";
-
-is $b->_buffer_size, 338, "Create buffer size";
-is $b->_buffer_count, 2, "Create buffer count";
-
-$b->clear_buffer;
-
-## CREATE DOCS##
-
-ok $b->create_docs(), 'Create_docs empty';
-
-ok $b->create_docs( { foo => 'bar' }, { foo => 'baz' } ), 'Create docs';
-
-cmp_deeply $b->_buffer,
- [ q({"create":{}}), q({"foo":"bar"}), q({"create":{}}), q({"foo":"baz"}) ],
- "Create docs in buffer";
-
-is $b->_buffer_size, 56, "Create docs buffer size";
-is $b->_buffer_count, 2, "Create docs buffer count";
-
-$b->clear_buffer;
-
-## DELETE ##
-ok $b->delete(), 'Delete empty';
-
-ok $b->delete(
- { index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- version => 1,
- version_type => 'external',
- },
- { _index => 'foo',
- _type => 'bar',
- _id => 2,
- _routing => 2,
- _parent => 2,
- _version => 1,
- _version_type => 'external',
- }
- ),
- 'Delete';
-
-cmp_deeply $b->_buffer,
- [
- q({"delete":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_type":"bar","_version":1,"_version_type":"external"}}),
- q({"delete":{"_id":2,"_index":"foo","_parent":2,"_routing":2,"_type":"bar","_version":1,"_version_type":"external"}}),
- ],
- "Delete in buffer";
-
-is $b->_buffer_size, 230, "Delete buffer size";
-is $b->_buffer_count, 2, "Delete buffer count";
-
-$b->clear_buffer;
-
-## DELETE IDS ##
-ok $b->delete_ids(), 'Delete IDs empty';
-
-ok $b->delete_ids( 1, 2, 3 ), 'Delete IDs';
-
-cmp_deeply $b->_buffer,
- [
- q({"delete":{"_id":1}}), q({"delete":{"_id":2}}),
- q({"delete":{"_id":3}}),
- ],
- "Delete IDs in buffer";
-
-is $b->_buffer_size, 63, "Delete IDs buffer size";
-is $b->_buffer_count, 3, "Delete IDS buffer count";
-
-$b->clear_buffer;
-
-## UPDATE ACTIONS ##
-ok $b->update(), 'Update empty';
-ok $b->update(
- { index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- timestamp => 1380019061000,
- ttl => '10m',
- version => 1,
- version_type => 'external',
- doc => { foo => 'bar' },
- doc_as_upsert => 1,
- },
- { index => 'foo',
- type => 'bar',
- id => 1,
- routing => 1,
- parent => 1,
- timestamp => 1380019061000,
- ttl => '10m',
- version => 1,
- version_type => 'external',
- upsert => { counter => 0 },
- script => '_ctx.source.counter+=incr',
- lang => 'mvel',
- params => { incr => 1 },
- },
- { _index => 'foo',
- _type => 'bar',
- _id => 1,
- _routing => 1,
- _parent => 1,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- doc => { foo => 'bar' },
- doc_as_upsert => 1,
- },
- { _index => 'foo',
- _type => 'bar',
- _id => 1,
- _routing => 1,
- _parent => 1,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- upsert => { counter => 0 },
- script => '_ctx.source.counter+=incr',
- lang => 'mvel',
- params => { incr => 1 },
- },
- { _index => 'foo',
- _type => 'bar',
- _id => 1,
- _routing => 1,
- _parent => 1,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- doc => { foo => 'bar' },
- doc_as_upsert => 1,
- detect_noop => 1,
- },
- { _index => 'foo',
- _type => 'bar',
- _id => 1,
- _routing => 1,
- _parent => 1,
- _timestamp => 1380019061000,
- _ttl => '10m',
- _version => 1,
- _version_type => 'external',
- upsert => { counter => 0 },
- script => '_ctx.source.counter+=incr',
- lang => 'mvel',
- params => { incr => 1 },
- detect_noop => 1,
- _retry_on_conflict => 3,
- },
- ),
- 'Update';
-
-cmp_deeply $b->_buffer,
- [
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"doc":{"foo":"bar"},"doc_as_upsert":1}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"lang":"mvel","params":{"incr":1},"script":"_ctx.source.counter+=incr","upsert":{"counter":0}}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"doc":{"foo":"bar"},"doc_as_upsert":1}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"lang":"mvel","params":{"incr":1},"script":"_ctx.source.counter+=incr","upsert":{"counter":0}}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"detect_noop":1,"doc":{"foo":"bar"},"doc_as_upsert":1}),
- q({"update":{"_id":1,"_index":"foo","_parent":1,"_routing":1,"_timestamp":1380019061000,"_ttl":"10m","_type":"bar","_version":1,"_version_type":"external"}}),
- q({"_retry_on_conflict":3,"detect_noop":1,"lang":"mvel","params":{"incr":1},"script":"_ctx.source.counter+=incr","upsert":{"counter":0}}),
- ],
- "Update in buffer";
-
-is $b->_buffer_size, 1393, "Update buffer size";
-is $b->_buffer_count, 6, "Update buffer count";
-
-$b->clear_buffer;
-
-done_testing;
diff --git a/t/Client_2_0/34_bulk_cxn_errors.t b/t/Client_2_0/34_bulk_cxn_errors.t
deleted file mode 100644
index 7fd0cfa..0000000
--- a/t/Client_2_0/34_bulk_cxn_errors.t
+++ /dev/null
@@ -1,36 +0,0 @@
-use Test::More;
-use Test::Deep;
-use Test::Exception;
-
-use strict;
-use warnings;
-use lib 't/lib';
-use Log::Any::Adapter;
-
-$ENV{ES_VERSION} = '2_0';
-$ENV{ES} = '10.255.255.1:9200';
-$ENV{ES_SKIP_PING} = 1;
-$ENV{ES_CXN_POOL} = 'Static';
-$ENV{ES_TIMEOUT} = 1;
-
-my $es = do "es_sync.pl" or die( $@ || $! );
-SKIP: {
- skip
- "IO::Socket::IP doesn't respect timeout: https://rt.cpan.org/Ticket/Display.html?id=103878",
- 3
- if $es->transport->cxn_pool->cxn_factory->cxn_class eq
- 'Search::Elasticsearch::Cxn::HTTPTiny'
- && $^V =~ /^v5.2\d/;
-
- # Check that the buffer is not cleared on a NoNodes exception
-
- my $b = $es->bulk_helper( index => 'foo', type => 'bar' );
- $b->create_docs( { foo => 'bar' } );
-
- is $b->_buffer_count, 1, "Buffer count pre-flush";
- throws_ok { $b->flush } 'Search::Elasticsearch::Error::NoNodes';
- is $b->_buffer_count, 1, "Buffer count post-flush";
-
-}
-
-done_testing;
diff --git a/t/Client_2_0/50_reindex.t b/t/Client_2_0/50_reindex.t
deleted file mode 100644
index 4161609..0000000
--- a/t/Client_2_0/50_reindex.t
+++ /dev/null
@@ -1,150 +0,0 @@
-use Test::More;
-use Test::Deep;
-use Test::Exception;
-use lib 't/lib';
-
-use strict;
-use warnings;
-
-$ENV{ES_VERSION} = '2_0';
-our $es = do "es_sync.pl" or die( $@ || $! );
-
-$es->indices->delete( index => '_all', ignore => 404 );
-do "index_test_data.pl" or die( $@ || $! );
-
-my $b;
-
-# Reindex to new index and new type
-$b = $es->bulk_helper(
- index => 'test2',
- type => 'test2'
-);
-$b->reindex( source => { index => 'test' } );
-$es->indices->refresh;
-
-is $es->count(
- index => 'test2',
- type => 'test2'
- )->{count}, 100,
- 'Reindexed to new index and type';
-
-# Reindex to same index
-$b = $es->bulk_helper();
-$b->reindex( source => { index => 'test' } );
-$es->indices->refresh;
-
-is $es->count(
- index => 'test',
- type => 'test'
- )->{count}, 100,
- 'Reindexed to same index';
-
-is $es->get( index => 'test', type => 'test', id => 1 )->{_version}, 2,
- "Reindexed to same index - version updated";
-
-# Reindex from generic source
-
-my @docs = map {
- { _index => 'foo', _type => 'bar', _id => $_, _source => { num => $_ } }
-} ( 1 .. 10 );
-
-$es->indices->delete( index => 'test2' );
-
-$b = $es->bulk_helper( index => 'test2' );
-$b->reindex( index => 'test2', source => sub { shift @docs } );
-$es->indices->refresh;
-
-is $es->count(
- index => 'test2',
- type => 'bar'
- )->{count}, 10,
- 'Reindexed from generic source';
-
-# Reindex with transform
-$es->indices->delete( index => 'test2' );
-
-$b = $es->bulk_helper( index => 'test2' );
-$b->reindex(
- source => { index => 'test' },
- transform => sub {
- my $doc = shift;
- return if $doc->{_source}{color} eq 'red';
- $doc->{_source}{transformed} = 1;
- return $doc;
- }
-);
-$es->indices->refresh;
-
-is $es->count(
- index => 'test2',
- type => 'test'
- )->{count}, 50,
- 'Transfrom - removed docs';
-
-my $query = {
- query => {
- bool => {
- must => [
- { term => { color => 'green' } },
- { term => { transformed => 1 } }
- ]
- }
- }
-};
-
-is $es->count(
- index => 'test2',
- type => 'test',
- body => $query,
- )->{count}, 50,
- 'Transfrom - transformed docs';
-
-# Reindex with parent & routing
-$es->indices->delete( index => '_all', ignore => 404 );
-for ( 'test', 'test2' ) {
- $es->indices->create(
- index => $_,
- body => { mappings => { test => { _parent => { type => 'foo' } } } }
- );
-}
-$es->cluster->health( wait_for_status => 'yellow' );
-
-for ( 1 .. 5 ) {
- $es->index(
- index => 'test',
- type => 'test',
- version_type => 'external',
- version => $_,
- id => $_,
- parent => 1,
- routing => 2,
- body => { count => $_ },
- );
-}
-$es->indices->refresh;
-
-$b = $es->bulk_helper( index => 'test2' );
-ok $b->reindex(
- version_type => 'external',
- source => {
- index => 'test',
- version => 1,
- fields => [ '_parent', '_routing', '_source' ]
- }
- ),
- "Advanced";
-
-$es->indices->refresh;
-my $results = $es->search(
- index => 'test2',
- type => 'test',
- sort => 'count',
- fields => [ '_parent', '_routing' ],
- version => 1,
-)->{hits}{hits};
-
-is $results->[3]{_parent}, 1, "Advanced - parent";
-is $results->[3]{_routing}, 2, "Advanced - routing";
-is $results->[3]{_version}, 4, "Advanced - version";
-
-done_testing;
diff --git a/t/Client_2_0/60_auth_httptiny.t b/t/Client_2_0/60_auth_httptiny.t
deleted file mode 100644
index 7a2c5e0..0000000
--- a/t/Client_2_0/60_auth_httptiny.t
+++ /dev/null
@@ -1,15 +0,0 @@
-use IO::Socket::SSL;
-use lib 't/lib';
-
-$ENV{ES_VERSION} = '2_0';
-$ENV{ES_CXN} = 'HTTPTiny';
-our $Throws_SSL = "SSL";
-
-sub ssl_options {
- return {
- SSL_verify_mode => SSL_VERIFY_PEER,
- SSL_ca_file => $_[0]
- };
-}
-
-do "es_sync_auth.pl" or die( $@ || $! );
diff --git a/t/Client_2_0/61_auth_lwp.t b/t/Client_2_0/61_auth_lwp.t
deleted file mode 100644
index ff9f0e6..0000000
--- a/t/Client_2_0/61_auth_lwp.t
+++ /dev/null
@@ -1,14 +0,0 @@
-use lib 't/lib';
-
-$ENV{ES_VERSION} = '2_0';
-$ENV{ES_CXN} = 'LWP';
-our $Throws_SSL = "Cxn";
-
-sub ssl_options {
- return {
- verify_hostname => 1,
- SSL_ca_file => $_[0]
- };
-}
-
-do "es_sync_auth.pl" or die( $@ || $! );
diff --git a/t/Client_7_0/00_print_version.t b/t/Client_7_0/00_print_version.t
new file mode 100644
index 0000000..e4f81d6
--- /dev/null
+++ b/t/Client_7_0/00_print_version.t
@@ -0,0 +1,40 @@
+# 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.
+
+use Test::More;
+use lib 't/lib';
+$ENV{ES_VERSION} = '7_0';
+my $es = do "es_sync.pl" or die( $@ || $! );
+
+eval {
+ my $v = $es->info->{version};
+ diag "";
+ diag "";
+ diag "Testing against Elasticsearch v" . $v->{number};
+ for ( sort keys %$v ) {
+ diag sprintf "%-20s: %s", $_, $v->{$_};
+ }
+ diag "";
+ diag "Client: " . ref($es);
+ diag "Cxn: " . $es->transport->cxn_pool->cxn_factory->cxn_class;
+ diag "GET Body: " . $es->transport->send_get_body_as;
+ diag "";
+ pass "ES Version";
+} or fail "ES Version";
+
+done_testing;
+
diff --git a/t/Client_7_0/10_live.t b/t/Client_7_0/10_live.t
new file mode 100644
index 0000000..df5f5eb
--- /dev/null
+++ b/t/Client_7_0/10_live.t
@@ -0,0 +1,45 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+my $es;
+$ENV{ES_VERSION} = '7_0';
+local $ENV{ES_CXN_POOL};
+
+$ENV{ES_CXN_POOL} = 'Static';
+$es = do "es_sync.pl" or die( $@ || $! );
+is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Static';
+
+$ENV{ES_CXN_POOL} = 'Static::NoPing';
+$es = do "es_sync.pl" or die( $@ || $! );
+is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Static::NoPing';
+
+unless ($ENV{ES} =~ /https/) {
+ $ENV{ES_CXN_POOL} = 'Sniff';
+ $es = do "es_sync.pl" or die( $@ || $! );
+ is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Sniff';
+
+ my ($node) = values %{ $es->transport->cxn_pool->next_cxn->sniff };
+ ok $node->{http}{max_content_length_in_bytes}, 'Sniffs max_content length';
+}
+done_testing;
diff --git a/t/Client_7_0/20_fork_httptiny.t b/t/Client_7_0/20_fork_httptiny.t
new file mode 100644
index 0000000..69794d7
--- /dev/null
+++ b/t/Client_7_0/20_fork_httptiny.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'HTTPTiny';
+do "es_sync_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_7_0/21_fork_lwp.t b/t/Client_7_0/21_fork_lwp.t
new file mode 100644
index 0000000..1323f1e
--- /dev/null
+++ b/t/Client_7_0/21_fork_lwp.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'LWP';
+do "es_sync_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_7_0/23_fork_netcurl.t b/t/Client_7_0/23_fork_netcurl.t
new file mode 100644
index 0000000..b38b49f
--- /dev/null
+++ b/t/Client_7_0/23_fork_netcurl.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'NetCurl';
+do "es_sync_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_7_0/30_bulk_add_action.t b/t/Client_7_0/30_bulk_add_action.t
new file mode 100644
index 0000000..126de0a
--- /dev/null
+++ b/t/Client_7_0/30_bulk_add_action.t
@@ -0,0 +1,234 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+my $es = do "es_sync.pl" or die( $@ || $! );
+my $b = $es->bulk_helper;
+
+$b->_serializer->_set_canonical;
+
+## EMPTY
+
+ok $b->add_action(), 'Empty add action';
+
+## INDEX ACTIONS ##
+
+ok $b->add_action(
+ index => {
+ index => 'foo',
+ id => 1,
+ pipeline => 'foo',
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ index => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+
+ }
+ ),
+ 'Add index actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"index":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"index":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Index actions in buffer";
+
+is $b->_buffer_size, 313, "Index actions buffer size";
+is $b->_buffer_count, 2, "Index actions buffer count";
+
+$b->clear_buffer;
+
+## CREATE ACTIONS ##
+
+ok $b->add_action(
+ create => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ pipeline => 'foo',
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ create => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+ }
+ ),
+ 'Add create actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"create":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"create":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Create actions in buffer";
+
+is $b->_buffer_size, 315, "Create actions buffer size";
+is $b->_buffer_count, 2, "Create actions buffer count";
+
+$b->clear_buffer;
+
+## DELETE ACTIONS ##
+
+ok $b->add_action(
+ delete => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ version => 1,
+ version_type => 'external',
+ },
+ delete => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _version => 1,
+ version_type => 'external',
+ }
+ ),
+ 'Add delete actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1,"_index":"foo","parent":1,"routing":1,"version":1,"version_type":"external"}}),
+ q({"delete":{"_id":2,"_index":"foo","parent":2,"routing":2,"version":1,"version_type":"external"}}),
+ ],
+ "Delete actions in buffer";
+
+is $b->_buffer_size, 194, "Delete actions buffer size";
+is $b->_buffer_count, 2, "Delete actions buffer count";
+
+$b->clear_buffer;
+
+## UPDATE ACTIONS ##
+
+ok $b->add_action(
+ update => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => ['foo'],
+ _source_excludes => ['bar'],
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ },
+ update => {
+ _index => 'foo',
+ _id => 1,
+ _routing => 1,
+ _parent => 1,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => ['foo'],
+ _source_excludes => ['bar'],
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ }
+ ),
+ 'Add update actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":["bar"],"_source_includes":["foo"],"detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"}),
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":["bar"],"_source_includes":["foo"],"detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"})
+ ],
+ "Update actions in buffer";
+
+is $b->_buffer_size, 710, "Update actions buffer size";
+is $b->_buffer_count, 2, "Update actions buffer count";
+
+$b->clear_buffer;
+
+## ERRORS ##
+throws_ok { $b->add_action( 'foo' => {} ) } qr/Unrecognised action/,
+ 'Bad action';
+
+throws_ok { $b->add_action( 'index', 'bar' ) } qr/Missing <params>/,
+ 'Missing params';
+
+throws_ok { $b->add_action( index => { } ) }
+qr/Missing .*<index>/, 'Missing index';
+throws_ok { $b->add_action( index => { index => 'i' } ) }
+qr/Missing <source>/, 'Missing source';
+
+throws_ok {
+ $b->add_action(
+ index => { index => 'i', source => {}, foo => 1 } );
+}
+qr/Unknown params/, 'Unknown params';
+
+done_testing;
diff --git a/t/Client_7_0/31_bulk_helpers.t b/t/Client_7_0/31_bulk_helpers.t
new file mode 100644
index 0000000..44ef0d0
--- /dev/null
+++ b/t/Client_7_0/31_bulk_helpers.t
@@ -0,0 +1,243 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+my $es = do "es_sync.pl" or die( $@ || $! );
+my $b = $es->bulk_helper(
+ index => 'i'
+);
+my $s = $b->_serializer;
+$s->_set_canonical;
+
+## INDEX ##
+
+ok $b->index(), 'Empty index';
+
+ok $b->index(
+ { index => 'foo',
+ id => 1,
+ pipeline => 'foo',
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+
+ }
+ ),
+ 'Index';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"index":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"index":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Index in buffer";
+
+is $b->_buffer_size, 313, "Index buffer size";
+is $b->_buffer_count, 2, "Index buffer count";
+
+$b->clear_buffer;
+
+## CREATE ##
+
+ok $b->create(), 'Create empty';
+
+ok $b->create(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ pipeline => 'foo',
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+ }
+ ),
+ 'Create';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"create":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"create":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Create actions in buffer";
+
+is $b->_buffer_size, 315, "Create actions buffer size";
+is $b->_buffer_count, 2, "Create actions buffer count";
+
+$b->clear_buffer;
+
+## CREATE DOCS##
+
+ok $b->create_docs(), 'Create_docs empty';
+
+ok $b->create_docs( { foo => 'bar' }, { foo => 'baz' } ), 'Create docs';
+
+cmp_deeply $b->_buffer,
+ [ q({"create":{}}), q({"foo":"bar"}), q({"create":{}}), q({"foo":"baz"}) ],
+ "Create docs in buffer";
+
+is $b->_buffer_size, 56, "Create docs buffer size";
+is $b->_buffer_count, 2, "Create docs buffer count";
+
+$b->clear_buffer;
+
+## DELETE ##
+ok $b->delete(), 'Delete empty';
+
+ok $b->delete(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ version => 1,
+ version_type => 'external',
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _version => 1,
+ version_type => 'external',
+ }
+ ),
+ 'Delete';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1,"_index":"foo","parent":1,"routing":1,"version":1,"version_type":"external"}}),
+ q({"delete":{"_id":2,"_index":"foo","parent":2,"routing":2,"version":1,"version_type":"external"}}),
+ ],
+ "Delete actions in buffer";
+
+is $b->_buffer_size, 194, "Delete actions buffer size";
+is $b->_buffer_count, 2, "Delete actions buffer count";
+
+$b->clear_buffer;
+
+## DELETE IDS ##
+ok $b->delete_ids(), 'Delete IDs empty';
+
+ok $b->delete_ids( 1, 2, 3 ), 'Delete IDs';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1}}), q({"delete":{"_id":2}}),
+ q({"delete":{"_id":3}}),
+ ],
+ "Delete IDs in buffer";
+
+is $b->_buffer_size, 63, "Delete IDs buffer size";
+is $b->_buffer_count, 3, "Delete IDS buffer count";
+
+$b->clear_buffer;
+
+## UPDATE ACTIONS ##
+ok $b->update(), 'Update empty';
+ok $b->update(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => ['foo'],
+ _source_excludes => ['bar'],
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ },
+ { _index => 'foo',
+ _id => 1,
+ _routing => 1,
+ _parent => 1,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => ['foo'],
+ _source_excludes => ['bar'],
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ }
+ ),
+ 'Update';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":["bar"],"_source_includes":["foo"],"detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"}),
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":["bar"],"_source_includes":["foo"],"detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"})
+ ],
+ "Update actions in buffer";
+
+is $b->_buffer_size, 710, "Update actions buffer size";
+is $b->_buffer_count, 2, "Update actions buffer count";
+
+$b->clear_buffer;
+
+done_testing;
diff --git a/t/Client_2_0/32_bulk_flush.t b/t/Client_7_0/32_bulk_flush.t
similarity index 71%
rename from t/Client_2_0/32_bulk_flush.t
rename to t/Client_7_0/32_bulk_flush.t
index 07cc133..143e35d 100644
--- a/t/Client_2_0/32_bulk_flush.t
+++ b/t/Client_7_0/32_bulk_flush.t
@@ -1,10 +1,27 @@
+# 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.
+
use Test::More;
use Test::Deep;
use strict;
use warnings;
use lib 't/lib';
-$ENV{ES_VERSION} = '2_0';
+$ENV{ES_VERSION} = '7_0';
my $es = do "es_sync.pl" or die( $@ || $! );
$es->indices->delete( index => '_all' );
@@ -68,8 +85,7 @@ sub test_flush {
my $params = shift;
my $b = $es->bulk_helper(
%$params,
- index => 'test',
- type => 'test'
+ index => 'test'
);
my @seq = @_;
diff --git a/t/Client_2_0/33_bulk_errors.t b/t/Client_7_0/33_bulk_errors.t
similarity index 68%
rename from t/Client_2_0/33_bulk_errors.t
rename to t/Client_7_0/33_bulk_errors.t
index 2ee6248..ae50680 100644
--- a/t/Client_2_0/33_bulk_errors.t
+++ b/t/Client_7_0/33_bulk_errors.t
@@ -1,3 +1,20 @@
+# 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.
+
use Test::More;
use Test::Deep;
use Test::Exception;
@@ -7,7 +24,7 @@ use warnings;
use lib 't/lib';
use Log::Any::Adapter;
-$ENV{ES_VERSION} = '2_0';
+$ENV{ES_VERSION} = '7_0';
my $es = do "es_sync.pl" or die( $@ || $! );
my $TRUE = $es->transport->serializer->decode('{"true":true}')->{true};
@@ -16,63 +33,26 @@ $es->indices->delete( index => '_all' );
my @Std = (
{ id => 1, source => { count => 1 } },
{ id => 1, source => { count => 'foo' } },
- { id => 1, version => 10, source => {} },
);
my ( $b, $success_count, $error_count, $custom_count, $conflict_count );
## Default error handling
-$b = bulk( { index => 'test', type => 'test' }, @Std );
-test_flush( "Default", 0, 2, 0, 0 );
+$b = bulk( { index => 'test' }, @Std );
+test_flush( "Default", 0, 1, 0, 0 );
## Custom error handling
$b = bulk(
{ index => 'test',
- type => 'test',
on_error => sub { $custom_count++ }
},
@Std
);
-test_flush( "Custom error", 0, 0, 2, 0 );
-
-# Conflict errors
-$b = bulk(
- { index => 'test',
- type => 'test',
- on_conflict => sub { $conflict_count++ }
- },
- @Std
-);
-test_flush( "Conflict error", 0, 1, 0, 1 );
-
-# Both error handling
-$b = bulk(
- { index => 'test',
- type => 'test',
- on_conflict => sub { $conflict_count++ },
- on_error => sub { $custom_count++ }
- },
- @Std
-);
-
-test_flush( "Conflict and custom", 0, 0, 1, 1 );
-
-# Conflict disable error
-$b = bulk(
- { index => 'test',
- type => 'test',
- on_conflict => sub { $conflict_count++ },
- on_error => undef
- },
- @Std
-);
-
-test_flush( "Conflict, error undef", 0, 0, 0, 1 );
+test_flush( "Custom error", 0, 0, 1, 0 );
# Disable both
$b = bulk(
{ index => 'test',
- type => 'test',
on_conflict => undef,
on_error => undef
},
@@ -84,34 +64,33 @@ test_flush( "Both undef", 0, 0, 0, 0 );
# Success
$b = bulk(
{ index => 'test',
- type => 'test',
on_success => sub { $success_count++ },
},
@Std
);
-test_flush( "Success", 1, 2, 0, 0 );
+test_flush( "Success", 1, 1, 0, 0 );
# cbs have correct params
$b = bulk(
{ index => 'test',
- type => 'test',
on_success => test_params(
'on_success',
- { _index => 'test',
- _type => 'test',
- _id => 1,
- _version => 1,
- status => 201,
- ok => $TRUE,
- _shards => { successful => 1, total => 2, failed => 0 }
+ { _index => 'test',
+ _id => 1,
+ _version => 1,
+ status => 201,
+ created => $TRUE,
+ result => 'created',
+ _shards => { successful => 1, total => 2, failed => 0 },
+ _primary_term => 1,
+ _seq_no => 0
},
0
),
on_error => test_params(
'on_error',
{ _index => 'test',
- _type => 'test',
_id => 1,
error => any(
re('MapperParsingException'),
@@ -124,7 +103,6 @@ $b = bulk(
on_conflict => test_params(
'on_conflict',
{ _index => 'test',
- _type => 'test',
_id => 1,
error => any(
re('version conflict'),
@@ -181,7 +159,6 @@ sub test_params {
return sub {
is $_[0], 'index', "$type - action";
- cmp_deeply $_[1], subhashof($result), "$type - result";
is $_[2], $j, "$type - array index";
is $_[3], $version, "$type - version";
};
diff --git a/t/Client_7_0/34_bulk_cxn_errors.t b/t/Client_7_0/34_bulk_cxn_errors.t
new file mode 100644
index 0000000..0f8d787
--- /dev/null
+++ b/t/Client_7_0/34_bulk_cxn_errors.t
@@ -0,0 +1,44 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Log::Any::Adapter;
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES} = '10.255.255.1:9200';
+$ENV{ES_SKIP_PING} = 1;
+$ENV{ES_CXN_POOL} = 'Static';
+$ENV{ES_TIMEOUT} = 1;
+
+my $es = do "es_sync.pl" or die( $@ || $! );
+
+# Check that the buffer is not cleared on a NoNodes exception
+
+my $b = $es->bulk_helper( index => 'foo', type => 'bar' );
+$b->create_docs( { foo => 'bar' } );
+
+is $b->_buffer_count, 1, "Buffer count pre-flush";
+throws_ok { $b->flush } 'Search::Elasticsearch::Error::NoNodes';
+is $b->_buffer_count, 1, "Buffer count post-flush";
+
+done_testing;
diff --git a/t/Client_2_0/40_scroll.t b/t/Client_7_0/40_scroll.t
similarity index 80%
rename from t/Client_2_0/40_scroll.t
rename to t/Client_7_0/40_scroll.t
index e1a3cd0..e1b0099 100644
--- a/t/Client_2_0/40_scroll.t
+++ b/t/Client_7_0/40_scroll.t
@@ -1,3 +1,20 @@
+# 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.
+
use Test::More;
use Test::Deep;
use Test::Exception;
@@ -6,7 +23,7 @@ use lib 't/lib';
use strict;
use warnings;
-$ENV{ES_VERSION} = '2_0';
+$ENV{ES_VERSION} = '7_0';
our $es = do "es_sync.pl" or die( $@ || $! );
$es->indices->delete( index => '_all', ignore => 404 );
@@ -24,7 +41,7 @@ test_scroll(
]
);
-do "index_test_data.pl" or die( $@ || $! );
+do "index_test_data_7.pl" or die( $@ || $! );
test_scroll(
"Match all",
@@ -57,29 +74,7 @@ test_scroll(
}
},
total => 50,
- max_score => num( 1.6, 0.5 ),
- aggs => bool(1),
- suggest => bool(1),
- steps => [
- next => [1],
- next_50 => [49],
- is_finished => 1,
- ]
-);
-
-test_scroll(
- "Scroll in qs",
- { scroll_in_qs => 1,
- body => {
- query => { term => { color => 'red' } },
- suggest => {
- mysuggest => { text => 'green', term => { field => 'color' } }
- },
- aggs => { switch => { terms => { field => 'switch' } } },
- }
- },
- total => 50,
- max_score => num( 1.6, 0.5 ),
+ max_score => num( 1, 0.5 ),
aggs => bool(1),
suggest => bool(1),
steps => [
@@ -89,27 +84,6 @@ test_scroll(
]
);
-test_scroll(
- "Scan",
- { search_type => 'scan',
- body => {
- suggest => {
- mysuggest => { text => 'green', term => { field => 'color' } }
- },
- }
- },
- total => 100,
- max_score => 0,
- suggest => bool(1),
- steps => [
- buffer_size => 0,
- next => [1],
- buffer_size => 49,
- next_100 => [99],
- is_finished => 1,
- ]
-);
-
test_scroll(
"Finish",
{},
@@ -209,7 +183,6 @@ sub test_scroll {
cmp_deeply $s->max_score, $tests{max_score}, "$title - max_score";
cmp_deeply $s->suggest, $tests{suggest}, "$title - suggest";
cmp_deeply $s->aggregations, $tests{aggs}, "$title - aggs";
-
my $i = 1;
my @steps = @{ $tests{steps} };
while ( my $name = shift @steps ) {
diff --git a/t/Client_7_0/60_auth_httptiny.t b/t/Client_7_0/60_auth_httptiny.t
new file mode 100644
index 0000000..3f3d94a
--- /dev/null
+++ b/t/Client_7_0/60_auth_httptiny.t
@@ -0,0 +1,32 @@
+# 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.
+
+use IO::Socket::SSL;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'HTTPTiny';
+our $Throws_SSL = "SSL";
+
+sub ssl_options {
+ return {
+ SSL_verify_mode => SSL_VERIFY_PEER,
+ SSL_ca_file => $_[0]
+ };
+}
+
+do "es_sync_auth.pl" or die( $@ || $! );
diff --git a/t/Client_7_0/61_auth_lwp.t b/t/Client_7_0/61_auth_lwp.t
new file mode 100644
index 0000000..faf4f29
--- /dev/null
+++ b/t/Client_7_0/61_auth_lwp.t
@@ -0,0 +1,31 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'LWP';
+our $Throws_SSL = "Cxn";
+
+sub ssl_options {
+ return {
+ verify_hostname => 1,
+ SSL_ca_file => $_[0]
+ };
+}
+
+do "es_sync_auth.pl" or die( $@ || $! );
diff --git a/t/Client_7_0/62_auth_netcurl.t b/t/Client_7_0/62_auth_netcurl.t
new file mode 100644
index 0000000..c2ccf5a
--- /dev/null
+++ b/t/Client_7_0/62_auth_netcurl.t
@@ -0,0 +1,32 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'NetCurl';
+use Net::Curl::Easy qw(
+ CURLOPT_CAINFO
+);
+
+our $Throws_SSL = "SSL";
+
+sub ssl_options {
+ return { CURLOPT_CAINFO() => $_[0] };
+}
+
+do "es_sync_auth.pl" or die( $@ || $! );
diff --git a/t/Client_7_0_Async/00_print_version.t b/t/Client_7_0_Async/00_print_version.t
new file mode 100644
index 0000000..037b50f
--- /dev/null
+++ b/t/Client_7_0_Async/00_print_version.t
@@ -0,0 +1,40 @@
+# 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.
+
+use Test::More;
+use lib 't/lib';
+$ENV{ES_VERSION} = '7_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+
+eval {
+ my $v = wait_for( $es->info )->{version};
+ diag "";
+ diag "";
+ diag "Testing against Search::Elasticsearch::Async v" . $v->{number};
+ for ( sort keys %$v ) {
+ diag sprintf "%-20s: %s", $_, $v->{$_};
+ }
+ diag "";
+ diag "Client: " . ref($es);
+ diag "Cxn: " . $es->transport->cxn_pool->cxn_factory->cxn_class;
+ diag "GET Body: " . $es->transport->send_get_body_as;
+ diag "";
+ pass "ES Version";
+} or fail "ES Version";
+
+done_testing;
+
diff --git a/t/Client_7_0_Async/10_live.t b/t/Client_7_0_Async/10_live.t
new file mode 100644
index 0000000..48c5bd4
--- /dev/null
+++ b/t/Client_7_0_Async/10_live.t
@@ -0,0 +1,58 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use AE;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+
+my $es;
+local $ENV{ES_CXN_POOL};
+
+$ENV{ES_CXN_POOL} = 'Async::Static';
+$es = do "es_async.pl" or die( $@ || $! );
+
+is wait_for( $es->info )->{tagline}, "You Know, for Search",
+ 'CxnPool::Async::Static';
+
+$ENV{ES_CXN_POOL} = 'Async::Static::NoPing';
+$es = do "es_async.pl" or die( $@ || $! );
+is wait_for( $es->info )->{tagline}, "You Know, for Search",
+ 'CxnPool::Async::Static::NoPing';
+
+$ENV{ES_CXN_POOL} = 'Async::Sniff';
+$es = do "es_async.pl" or die( $@ || $! );
+is wait_for( $es->info )->{tagline}, "You Know, for Search",
+ 'CxnPool::Async::Sniff';
+
+my ($node) = values %{
+ ( wait_for(
+ $es->transport->cxn_pool->next_cxn->then(
+ sub { shift()->sniff }
+ )
+ )
+ )[1]
+};
+ok $node->{http}{max_content_length_in_bytes}, 'Sniffs max_content length';
+
+done_testing;
+
diff --git a/t/Client_7_0_Async/20_fork_aehttp.t b/t/Client_7_0_Async/20_fork_aehttp.t
new file mode 100644
index 0000000..4f13749
--- /dev/null
+++ b/t/Client_7_0_Async/20_fork_aehttp.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'AEHTTP';
+do "es_async_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_7_0_Async/21_fork_mojo.t b/t/Client_7_0_Async/21_fork_mojo.t
new file mode 100644
index 0000000..0185b31
--- /dev/null
+++ b/t/Client_7_0_Async/21_fork_mojo.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'Mojo';
+do "es_async_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_7_0_Async/30_bulk_add_action.t b/t/Client_7_0_Async/30_bulk_add_action.t
new file mode 100644
index 0000000..6579b24
--- /dev/null
+++ b/t/Client_7_0_Async/30_bulk_add_action.t
@@ -0,0 +1,233 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+my ( $error, $name );
+
+$ENV{ES_VERSION} = '7_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+
+my $b = $es->bulk_helper(
+ on_fatal => sub {
+ like shift(), qr/$error/, $name;
+ }
+);
+
+$b->_serializer->_set_canonical;
+
+## EMPTY
+
+ok $b->add_action(), 'Empty add action';
+
+## INDEX ACTIONS ##
+
+ok $b->add_action(
+ index => {
+ index => 'foo',
+ id => 1,
+ pipeline => 'foo',
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ index => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+
+ }
+ ),
+ 'Add index actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"index":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"index":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Index actions in buffer";
+
+is $b->_buffer_size, 313, "Index actions buffer size";
+is $b->_buffer_count, 2, "Index actions buffer count";
+
+$b->clear_buffer;
+
+## CREATE ACTIONS ##
+
+ok $b->add_action(
+ create => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ pipeline => 'foo',
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ create => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+ }
+ ),
+ 'Add create actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"create":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"create":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Create actions in buffer";
+
+is $b->_buffer_size, 315, "Create actions buffer size";
+is $b->_buffer_count, 2, "Create actions buffer count";
+
+$b->clear_buffer;
+
+## DELETE ACTIONS ##
+
+ok $b->add_action(
+ delete => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ version => 1,
+ version_type => 'external',
+ },
+ delete => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _version => 1,
+ version_type => 'external',
+ }
+ ),
+ 'Add delete actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1,"_index":"foo","parent":1,"routing":1,"version":1,"version_type":"external"}}),
+ q({"delete":{"_id":2,"_index":"foo","parent":2,"routing":2,"version":1,"version_type":"external"}}),
+ ],
+ "Delete actions in buffer";
+
+is $b->_buffer_size, 194, "Delete actions buffer size";
+is $b->_buffer_count, 2, "Delete actions buffer count";
+
+$b->clear_buffer;
+
+## UPDATE ACTIONS ##
+
+ok $b->add_action(
+ update => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ _source => 'true',
+ _source_includes => 'foo',
+ _source_excludes => 'bar',
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ },
+ update => {
+ _index => 'foo',
+ _id => 1,
+ _routing => 1,
+ _source => 'true',
+ _source_includes => 'foo',
+ _source_excludes => 'bar',
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ }
+ ),
+ 'Add update actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"update":{"_id":1,"_index":"foo","routing":1}}),
+ q({"_source":"true","_source_excludes":"bar","_source_includes":"foo","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"}),
+ q({"update":{"_id":1,"_index":"foo","routing":1}}),
+ q({"_source":"true","_source_excludes":"bar","_source_includes":"foo","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"})
+ ],
+ "Update actions in buffer";
+
+is $b->_buffer_size, 486, "Update actions buffer size";
+is $b->_buffer_count, 2, "Update actions buffer count";
+
+$b->clear_buffer;
+
+## ERRORS ##
+$error = "Unrecognised action";
+$name = 'Bad action';
+$b->add_action( 'foo' => {} );
+
+$error = "Missing <params>";
+$name = 'Missing params';
+$b->add_action( 'index', 'bar' );
+
+$error = "Missing .*<index>";
+$name = 'Missing index';
+$b->add_action( index => { type => 't' } );
+
+$error = "Missing .*<source>";
+$name = 'Missing source';
+$b->add_action( index => { index => 'i', type => 't' } );
+
+$error = "Unknown params";
+$name = 'Unknown params';
+$b->add_action(
+ index => { index => 'i', type => 't', source => {}, foo => 1 } );
+
+done_testing;
diff --git a/t/Client_7_0_Async/31_bulk_helpers.t b/t/Client_7_0_Async/31_bulk_helpers.t
new file mode 100644
index 0000000..5d1c5fe
--- /dev/null
+++ b/t/Client_7_0_Async/31_bulk_helpers.t
@@ -0,0 +1,245 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+
+my $b = $es->bulk_helper(
+ index => 'i',
+ type => 't'
+);
+my $s = $b->_serializer;
+$s->_set_canonical;
+
+## INDEX ##
+
+ok $b->index(), 'Empty index';
+
+ok $b->index(
+ { index => 'foo',
+ id => 1,
+ pipeline => 'foo',
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+
+ }
+ ),
+ 'Index';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"index":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"index":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Index in buffer";
+
+is $b->_buffer_size, 313, "Index buffer size";
+is $b->_buffer_count, 2, "Index buffer count";
+
+$b->clear_buffer;
+
+## CREATE ##
+
+ok $b->create(), 'Create empty';
+
+ok $b->create(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ pipeline => 'foo',
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+ }
+ ),
+ 'Create';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"create":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"create":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Create actions in buffer";
+
+is $b->_buffer_size, 315, "Create actions buffer size";
+is $b->_buffer_count, 2, "Create actions buffer count";
+
+$b->clear_buffer;
+
+## CREATE DOCS##
+
+ok $b->create_docs(), 'Create_docs empty';
+
+ok $b->create_docs( { foo => 'bar' }, { foo => 'baz' } ), 'Create docs';
+
+cmp_deeply $b->_buffer,
+ [ q({"create":{}}), q({"foo":"bar"}), q({"create":{}}), q({"foo":"baz"}) ],
+ "Create docs in buffer";
+
+is $b->_buffer_size, 56, "Create docs buffer size";
+is $b->_buffer_count, 2, "Create docs buffer count";
+
+$b->clear_buffer;
+
+## DELETE ##
+ok $b->delete(), 'Delete empty';
+
+ok $b->delete(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ version => 1,
+ version_type => 'external',
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _version => 1,
+ version_type => 'external',
+ }
+ ),
+ 'Delete';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1,"_index":"foo","parent":1,"routing":1,"version":1,"version_type":"external"}}),
+ q({"delete":{"_id":2,"_index":"foo","parent":2,"routing":2,"version":1,"version_type":"external"}}),
+ ],
+ "Delete actions in buffer";
+
+is $b->_buffer_size, 194, "Delete actions buffer size";
+is $b->_buffer_count, 2, "Delete actions buffer count";
+
+$b->clear_buffer;
+
+## DELETE IDS ##
+ok $b->delete_ids(), 'Delete IDs empty';
+
+ok $b->delete_ids( 1, 2, 3 ), 'Delete IDs';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1}}), q({"delete":{"_id":2}}),
+ q({"delete":{"_id":3}}),
+ ],
+ "Delete IDs in buffer";
+
+is $b->_buffer_size, 63, "Delete IDs buffer size";
+is $b->_buffer_count, 3, "Delete IDS buffer count";
+
+$b->clear_buffer;
+
+## UPDATE ACTIONS ##
+ok $b->update(), 'Update empty';
+ok $b->update(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => 'foo',
+ _source_excludes => 'bar',
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ },
+ { _index => 'foo',
+ _id => 1,
+ _routing => 1,
+ _parent => 1,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => 'foo',
+ _source_excludes => 'bar',
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ }
+ ),
+ 'Update';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":"bar","_source_includes":"foo","detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"}),
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":"bar","_source_includes":"foo","detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"})
+ ],
+ "Update actions in buffer";
+
+is $b->_buffer_size, 702, "Update actions buffer size";
+is $b->_buffer_count, 2, "Update actions buffer count";
+
+$b->clear_buffer;
+
+done_testing;
diff --git a/t/Client_7_0_Async/32_bulk_flush.t b/t/Client_7_0_Async/32_bulk_flush.t
new file mode 100644
index 0000000..77b29fa
--- /dev/null
+++ b/t/Client_7_0_Async/32_bulk_flush.t
@@ -0,0 +1,147 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use strict;
+use warnings;
+use lib 't/lib';
+use AE;
+use Promises qw(deferred);
+
+$ENV{ES_VERSION} = '7_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+
+wait_for( $es->indices->delete( index => '_all' ) );
+
+test_flush(
+ "max count", #
+ { max_count => 3 }, #
+ 1, 2, 0, 1, 2, 0, 1, 2, 0, 1
+);
+
+test_flush(
+ "max size", #
+ { max_size => 95 }, #
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max size > max_count",
+ { max_size => 95, max_count => 3 },
+ 1, 2, 0, 1, 2, 0, 1, 2, 0, 1
+);
+
+test_flush(
+ "max size < max_count",
+ { max_size => 95, max_count => 5 },
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max size = 0, max_count",
+ { max_size => 0, max_count => 5 },
+ 1, 2, 3, 4, 0, 1, 2, 3, 4, 0
+);
+
+test_flush(
+ "max count = 0, max_size",
+ { max_size => 95, max_count => 0 },
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max count = 0, max_size = 0",
+ { max_size => 0, max_count => 0 },
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
+);
+
+test_flush(
+ "max_count = 5, max_time = 5",
+ { max_count => 5, max_time => 5 },
+ 1, 2, 0, 1, 2, 3, 4, 0, 0, 1
+);
+
+done_testing;
+
+wait_for( $es->indices->delete( index => 'test' ) );
+
+#===================================
+sub test_flush {
+#===================================
+ my $title = shift;
+ my $params = shift;
+ my $b = $es->bulk_helper(
+ %$params,
+ index => 'test'
+ );
+
+ my @seq = @_;
+ my $cv = AE::cv;
+
+ my $i = 10;
+ my $loop;
+
+ my $index_doc = sub {
+ $b->index( { id => $i, source => {} } );
+ };
+
+ my $check_buffer = sub {
+ is $b->_buffer_count, shift @seq, "$title - " . ( $i - 9 );
+ $i++;
+ };
+
+ my $d = deferred;
+ my $w;
+
+ $loop = sub {
+ if ( $i == 20 ) {
+ return $b->flush->then( sub { $d->resolve } );
+ }
+
+ # sleep on 12 or 18 if max_time specified
+ if ( $params->{max_time} && ( $i == 12 || $i == 18 ) ) {
+ $b->_last_flush( time - $params->{max_time} - 1 );
+ }
+ $index_doc->()->then($check_buffer)->then($loop);
+ };
+
+ $es->indices->delete( index => 'test', ignore => 404 )
+ ->then( sub { $es->indices->create( index => 'test' ) } )
+ ->then( sub { $es->cluster->health( wait_for_status => 'yellow' ) } )
+ ->then($loop);
+
+ $d->promise->then(
+ sub {
+ is $b->_buffer_count, 0, "$title - final flush";
+ $es->indices->refresh;
+ }
+ )->then(
+ sub {
+ $es->count;
+ }
+ )->then(
+ sub {
+ is shift()->{count}, 10, "$title - all docs indexed";
+ }
+ )->then(
+ sub {
+ $cv->send;
+ }
+ )->catch( sub { $cv->croak(@_) } );
+ $cv->recv;
+}
diff --git a/t/Client_7_0_Async/33_bulk_errors.t b/t/Client_7_0_Async/33_bulk_errors.t
new file mode 100644
index 0000000..d2cdb70
--- /dev/null
+++ b/t/Client_7_0_Async/33_bulk_errors.t
@@ -0,0 +1,212 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use AE;
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Log::Any::Adapter;
+
+$ENV{ES_VERSION} = '7_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+my $TRUE = $es->transport->serializer->decode('{"true":true}')->{true};
+
+my $cv = AE::cv;
+
+wait_for( $es->indices->delete( index => '_all' ) );
+
+my @Std = (
+ { id => 1, source => { count => 1 } },
+ { id => 1, source => { count => 'foo' } },
+ { id => 1, source => {} },
+);
+
+my ( $b, $error, $success_count, $error_count, $custom_count, $conflict_count );
+
+## Default error handling
+$b = bulk( { index => 'test'}, @Std );
+test_flush( "Default", 0, 1, 0, 0 );
+
+## Custom error handling
+$b = bulk(
+ { index => 'test',
+ on_error => sub { $custom_count++ }
+ },
+ @Std
+);
+test_flush( "Custom error", 0, 0, 1, 0 );
+
+# Conflict errors
+$b = bulk(
+ { index => 'test',
+ on_conflict => sub { $conflict_count++ }
+ },
+ @Std
+);
+test_flush( "Conflict error", 0, 1, 0, 0 );
+
+# Both error handling
+$b = bulk(
+ { index => 'test',
+ on_conflict => sub { $conflict_count++ },
+ on_error => sub { $custom_count++ }
+ },
+ @Std
+);
+
+test_flush( "Conflict and custom", 0, 0, 1, 0 );
+
+# Conflict disable error
+$b = bulk(
+ { index => 'test',
+ on_conflict => sub { $conflict_count++ },
+ on_error => undef
+ },
+ @Std
+);
+
+test_flush( "Conflict, error undef", 0, 0, 0, 0 );
+
+# Disable both
+$b = bulk(
+ { index => 'test',
+ on_conflict => undef,
+ on_error => undef
+ },
+ @Std
+);
+
+test_flush( "Both undef", 0, 0, 0, 0 );
+
+# Success
+$b = bulk(
+ { index => 'test',
+ on_success => sub { $success_count++ },
+ },
+ @Std
+);
+
+test_flush( "Success", 2, 1, 0, 0 );
+
+# cbs have correct params
+$b = bulk(
+ { index => 'test',
+ on_success => test_params(
+ 'on_success',
+ { _index => 'test',
+ _type => '_doc',
+ _id => 1,
+ _version => 1,
+ created => $TRUE,
+ _shards => { successful => 1, total => 2, failed => 0 },
+ _primary_term => 1,
+ _seq_no => 0,
+ },
+ 0
+ ),
+ on_error => test_params(
+ 'on_error',
+ { _index => 'test',
+ _type => '_doc',
+ _id => 1,
+ error => any(
+ re('MapperParsingException'),
+ superhashof( { type => 'mapper_parsing_exception' } )
+ ),
+ status => 400,
+ },
+ 1
+ ),
+ on_conflict => test_params(
+ 'on_conflict',
+ { _index => 'test',
+ _type => '_doc',
+ _id => 1,
+ error => any(
+ re('version conflict'),
+ superhashof(
+ { type => 'version_conflict_engine_exception' }
+ )
+ ),
+ status => 409,
+ },
+ 2, 1
+ ),
+ },
+ @Std
+);
+wait_for( $b->flush );
+
+done_testing;
+
+wait_for( $es->indices->delete( index => '_all' ) );
+
+#===================================
+sub bulk {
+#===================================
+ my ( $params, @docs ) = @_;
+ my $b = $es->bulk_helper(
+ on_fatal => sub { $error = shift(); $error_count++ },
+ %$params,
+ );
+
+ $error = '';
+
+ wait_for(
+ $es->indices->delete( index => 'test', ignore => 404 ) #
+ ->then( sub { $es->indices->create( index => 'test' ) } ) #
+ ->then(
+ sub { $es->cluster->health( wait_for_status => 'yellow' ) }
+ ) #
+ ->then( sub { $b->index(@docs) } )
+ );
+ return $b;
+}
+
+#===================================
+sub test_flush {
+#===================================
+ my ( $title, $success, $default, $custom, $conflict ) = @_;
+ $success_count = $custom_count = $error_count = $conflict_count = 0;
+ {
+ local $SIG{__WARN__} = sub { $error_count++ };
+ wait_for( $b->flush );
+ }
+ is $success_count, $success, "$title - success";
+ is $error_count, $default, "$title - default";
+ is $custom_count, $custom, "$title - custom";
+ is $conflict_count, $conflict, "$title - conflict";
+
+}
+
+#===================================
+sub test_params {
+#===================================
+ my ( $type, $result, $j, $version ) = @_;
+
+ return sub {
+ is $_[0], 'index', "$type - action";
+ cmp_deeply subhashof($result), $_[1], "$type - result";
+ is $_[2], $j, "$type - array index";
+ is $_[3], $version, "$type - version";
+ };
+}
+
diff --git a/t/Client_7_0_Async/34_bulk_cxn_errors.t b/t/Client_7_0_Async/34_bulk_cxn_errors.t
new file mode 100644
index 0000000..5ba61fe
--- /dev/null
+++ b/t/Client_7_0_Async/34_bulk_cxn_errors.t
@@ -0,0 +1,53 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use AE;
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Log::Any::Adapter;
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES} = '10.255.255.1:9200';
+$ENV{ES_SKIP_PING} = 1;
+$ENV{ES_CXN_POOL} = 'Async::Static';
+
+my $es = do "es_async.pl" or die( $@ || $! );
+my $error;
+my $b = $es->bulk_helper( index => 'foo', type => 'bar' );
+$b->create_docs( { foo => 'bar' } );
+
+# Check that the buffer is not cleared on a NoNodes exception
+
+is $b->_buffer_count, 1, "Buffer count pre-flush";
+
+wait_for(
+ $b->flush->catch(
+ sub {
+ my $error = shift;
+ isa_ok $error, 'Search::Elasticsearch::Error::NoNodes';
+ }
+ )
+);
+
+is $b->_buffer_count, 1, "Buffer count post-flush";
+
+done_testing;
diff --git a/t/Client_7_0_Async/40_scroll.t b/t/Client_7_0_Async/40_scroll.t
new file mode 100644
index 0000000..ba99fe0
--- /dev/null
+++ b/t/Client_7_0_Async/40_scroll.t
@@ -0,0 +1,205 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use lib 't/lib';
+
+use strict;
+use warnings;
+
+our ( $s, $total_seen, $max_seen );
+$ENV{ES_VERSION} = '7_0';
+our $es = do "es_async.pl" or die( $@ || $! );
+
+wait_for( $es->indices->delete( index => '_all', ignore => 404 ) );
+
+test_scroll(
+ "No indices",
+ { on_results => \&on_results },
+ total => 0,
+ max_score => 0,
+ total_seen => 0,
+ max_seen => 0,
+);
+
+do "index_test_data_7.pl" or die( $@ || $! );
+
+test_scroll(
+ "Match all - on_result",
+ { on_result => \&on_results, size => 10 },
+ total => 100,
+ max_score => 1,
+ total_seen => 100,
+ max_seen => 1
+);
+
+test_scroll(
+ "Match all - on_results",
+ { on_results => \&on_results, size => 10 },
+ total => 100,
+ max_score => 1,
+ total_seen => 100,
+ max_seen => 10
+);
+
+test_scroll(
+ "Query",
+ { body => {
+ query => { term => { color => 'red' } },
+ suggest => {
+ mysuggest => { text => 'green', term => { field => 'color' } }
+ },
+ aggs => { switch => { terms => { field => 'switch' } } },
+ },
+ size => 10,
+ on_results => \&on_results
+ },
+ total => 50,
+ max_score => num( 1.0, 0.5 ),
+ aggs => bool(1),
+ suggest => bool(1),
+ total_seen => 50,
+ max_seen => 10
+);
+
+test_scroll(
+ "Finish",
+ { on_results => sub {
+ on_results(@_);
+ $s->finish if $total_seen == 30;
+ },
+ size => 10
+ },
+ total => 100,
+ max_score => 1,
+ total_seen => 30,
+ max_seen => 10
+);
+
+{
+ # Test auto finish fork protection.
+ my $count = 0;
+ my $s = $es->scroll_helper( size => 5, on_result => sub { $count++ } );
+
+ my $pid = fork();
+ unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+ unless ($pid) {
+
+ # Child. Call finish check that its not finished
+ # (the call to finish did nothing).
+ wait_for( $s->finish() );
+ exit 0;
+ }
+ else {
+ # Wait for children
+ waitpid( $pid, 0 );
+ is $?, 0, "Child exited without errors";
+ }
+ ok !$s->is_finished(), "Our Scroll is not finished";
+ wait_for( $s->start );
+ is $count, 100, "All documents retrieved";
+ ok $s->is_finished, "Our scroll is finished";
+}
+
+# {
+# # Test Scroll usage attempt in a different process.
+# my $count = 0;
+# my $s = $es->scroll_helper(
+# size => 5,
+# on_result => sub { $count++ },
+# on_error => sub { die @_ }
+# );
+
+# my $pid = fork();
+# unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+# unless ($pid) {
+
+# eval { wait_for( $s->start ) };
+# my $err = $@;
+# exit( eval { $err->is('Illegal') && 123 } || 999 );
+# }
+# else {
+# # Wait for children
+# waitpid( $pid, 0 );
+# is $? >> 8, 123, "Child threw Illegal exception";
+# }
+# }
+
+# {
+# # Test valid Scroll usage after initial fork
+# my $pid = fork();
+# unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+# unless ($pid) {
+
+# my $count = 0;
+# my $s = $es->scroll_helper(
+# size => 5,
+# on_result => sub { $count++ },
+# on_error => sub { die @_ }
+# );
+
+# wait_for( $s->start );
+# exit 0;
+# }
+# else {
+# # Wait for children
+# waitpid( $pid, 0 );
+# is $? , 0, "Scroll completed successfully";
+# }
+# }
+
+done_testing;
+
+wait_for( $es->indices->delete( index => 'test' ) );
+
+#===================================
+sub test_scroll {
+#===================================
+ my ( $title, $params, %tests ) = @_;
+ $max_seen = $total_seen = 0;
+ subtest $title => sub {
+ $s = $es->scroll_helper(
+ on_start => sub { test_start( $title, \%tests, @_ ) },
+ %$params
+ );
+ wait_for( $s->start );
+
+ is $total_seen, $tests{total_seen}, "$title - total seen";
+ is $max_seen, $tests{max_seen}, "$title - max seen";
+
+ };
+}
+
+#===================================
+sub test_start {
+#===================================
+ my ( $title, $tests, $s ) = @_;
+ is $s->total, $tests->{total}, "$title - total";
+ cmp_deeply $s->max_score, $tests->{max_score}, "$title - max_score";
+ cmp_deeply $s->suggest, $tests->{suggest}, "$title - suggest";
+ cmp_deeply $s->aggregations, $tests->{aggs}, "$title - aggs";
+
+}
+
+#===================================
+sub on_results {
+#===================================
+ $max_seen = @_ if @_ > $max_seen;
+ $total_seen += @_;
+}
diff --git a/t/Client_7_0_Async/60_auth_aehttp.t b/t/Client_7_0_Async/60_auth_aehttp.t
new file mode 100644
index 0000000..6323079
--- /dev/null
+++ b/t/Client_7_0_Async/60_auth_aehttp.t
@@ -0,0 +1,31 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'AEHTTP';
+
+sub ssl_options {
+ return {
+ verify => 1,
+ verify_peername => 'https',
+ ca_file => $_[0]
+ };
+}
+
+do "es_async_auth.pl" or die( $@ || $! );
diff --git a/t/Client_7_0_Async/61_auth_mojo.t b/t/Client_7_0_Async/61_auth_mojo.t
new file mode 100644
index 0000000..4d2cdd9
--- /dev/null
+++ b/t/Client_7_0_Async/61_auth_mojo.t
@@ -0,0 +1,27 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '7_0';
+$ENV{ES_CXN} = 'Mojo';
+
+sub ssl_options {
+ return { ca => $_[0] };
+}
+
+do "es_async_auth.pl" or die( $@ || $! );
diff --git a/t/Client_8_0/00_print_version.t b/t/Client_8_0/00_print_version.t
new file mode 100644
index 0000000..d0b7bd2
--- /dev/null
+++ b/t/Client_8_0/00_print_version.t
@@ -0,0 +1,40 @@
+# 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.
+
+use Test::More;
+use lib 't/lib';
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_sync.pl" or die( $@ || $! );
+
+eval {
+ my $v = $es->info->{version};
+ diag "";
+ diag "";
+ diag "Testing against Elasticsearch v" . $v->{number};
+ for ( sort keys %$v ) {
+ diag sprintf "%-20s: %s", $_, $v->{$_};
+ }
+ diag "";
+ diag "Client: " . ref($es);
+ diag "Cxn: " . $es->transport->cxn_pool->cxn_factory->cxn_class;
+ diag "GET Body: " . $es->transport->send_get_body_as;
+ diag "";
+ pass "ES Version";
+} or fail "ES Version";
+
+done_testing;
+
diff --git a/t/Client_8_0/10_live.t b/t/Client_8_0/10_live.t
new file mode 100644
index 0000000..54abc57
--- /dev/null
+++ b/t/Client_8_0/10_live.t
@@ -0,0 +1,45 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+my $es;
+$ENV{ES_VERSION} = '8_0';
+local $ENV{ES_CXN_POOL};
+
+$ENV{ES_CXN_POOL} = 'Static';
+$es = do "es_sync.pl" or die( $@ || $! );
+is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Static';
+
+$ENV{ES_CXN_POOL} = 'Static::NoPing';
+$es = do "es_sync.pl" or die( $@ || $! );
+is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Static::NoPing';
+
+unless ($ENV{ES} =~ /https/) {
+ $ENV{ES_CXN_POOL} = 'Sniff';
+ $es = do "es_sync.pl" or die( $@ || $! );
+ is $es->info->{tagline}, "You Know, for Search", 'CxnPool::Sniff';
+
+ my ($node) = values %{ $es->transport->cxn_pool->next_cxn->sniff };
+ ok $node->{http}{max_content_length_in_bytes}, 'Sniffs max_content length';
+}
+done_testing;
diff --git a/t/Client_8_0/20_fork_httptiny.t b/t/Client_8_0/20_fork_httptiny.t
new file mode 100644
index 0000000..ccdf931
--- /dev/null
+++ b/t/Client_8_0/20_fork_httptiny.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'HTTPTiny';
+do "es_sync_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_8_0/21_fork_lwp.t b/t/Client_8_0/21_fork_lwp.t
new file mode 100644
index 0000000..a362ff9
--- /dev/null
+++ b/t/Client_8_0/21_fork_lwp.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'LWP';
+do "es_sync_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_8_0/23_fork_netcurl.t b/t/Client_8_0/23_fork_netcurl.t
new file mode 100644
index 0000000..23182ff
--- /dev/null
+++ b/t/Client_8_0/23_fork_netcurl.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'NetCurl';
+do "es_sync_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_8_0/30_bulk_add_action.t b/t/Client_8_0/30_bulk_add_action.t
new file mode 100644
index 0000000..6a8015b
--- /dev/null
+++ b/t/Client_8_0/30_bulk_add_action.t
@@ -0,0 +1,234 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_sync.pl" or die( $@ || $! );
+my $b = $es->bulk_helper;
+
+$b->_serializer->_set_canonical;
+
+## EMPTY
+
+ok $b->add_action(), 'Empty add action';
+
+## INDEX ACTIONS ##
+
+ok $b->add_action(
+ index => {
+ index => 'foo',
+ id => 1,
+ pipeline => 'foo',
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ index => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+
+ }
+ ),
+ 'Add index actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"index":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"index":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Index actions in buffer";
+
+is $b->_buffer_size, 313, "Index actions buffer size";
+is $b->_buffer_count, 2, "Index actions buffer count";
+
+$b->clear_buffer;
+
+## CREATE ACTIONS ##
+
+ok $b->add_action(
+ create => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ pipeline => 'foo',
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ create => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+ }
+ ),
+ 'Add create actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"create":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"create":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Create actions in buffer";
+
+is $b->_buffer_size, 315, "Create actions buffer size";
+is $b->_buffer_count, 2, "Create actions buffer count";
+
+$b->clear_buffer;
+
+## DELETE ACTIONS ##
+
+ok $b->add_action(
+ delete => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ version => 1,
+ version_type => 'external',
+ },
+ delete => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _version => 1,
+ version_type => 'external',
+ }
+ ),
+ 'Add delete actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1,"_index":"foo","parent":1,"routing":1,"version":1,"version_type":"external"}}),
+ q({"delete":{"_id":2,"_index":"foo","parent":2,"routing":2,"version":1,"version_type":"external"}}),
+ ],
+ "Delete actions in buffer";
+
+is $b->_buffer_size, 194, "Delete actions buffer size";
+is $b->_buffer_count, 2, "Delete actions buffer count";
+
+$b->clear_buffer;
+
+## UPDATE ACTIONS ##
+
+ok $b->add_action(
+ update => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => ['foo'],
+ _source_excludes => ['bar'],
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ },
+ update => {
+ _index => 'foo',
+ _id => 1,
+ _routing => 1,
+ _parent => 1,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => ['foo'],
+ _source_excludes => ['bar'],
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ }
+ ),
+ 'Add update actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":["bar"],"_source_includes":["foo"],"detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"}),
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":["bar"],"_source_includes":["foo"],"detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"})
+ ],
+ "Update actions in buffer";
+
+is $b->_buffer_size, 710, "Update actions buffer size";
+is $b->_buffer_count, 2, "Update actions buffer count";
+
+$b->clear_buffer;
+
+## ERRORS ##
+throws_ok { $b->add_action( 'foo' => {} ) } qr/Unrecognised action/,
+ 'Bad action';
+
+throws_ok { $b->add_action( 'index', 'bar' ) } qr/Missing <params>/,
+ 'Missing params';
+
+throws_ok { $b->add_action( index => { } ) }
+qr/Missing .*<index>/, 'Missing index';
+throws_ok { $b->add_action( index => { index => 'i' } ) }
+qr/Missing <source>/, 'Missing source';
+
+throws_ok {
+ $b->add_action(
+ index => { index => 'i', source => {}, foo => 1 } );
+}
+qr/Unknown params/, 'Unknown params';
+
+done_testing;
diff --git a/t/Client_8_0/31_bulk_helpers.t b/t/Client_8_0/31_bulk_helpers.t
new file mode 100644
index 0000000..5574cf9
--- /dev/null
+++ b/t/Client_8_0/31_bulk_helpers.t
@@ -0,0 +1,243 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_sync.pl" or die( $@ || $! );
+my $b = $es->bulk_helper(
+ index => 'i'
+);
+my $s = $b->_serializer;
+$s->_set_canonical;
+
+## INDEX ##
+
+ok $b->index(), 'Empty index';
+
+ok $b->index(
+ { index => 'foo',
+ id => 1,
+ pipeline => 'foo',
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+
+ }
+ ),
+ 'Index';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"index":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"index":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Index in buffer";
+
+is $b->_buffer_size, 313, "Index buffer size";
+is $b->_buffer_count, 2, "Index buffer count";
+
+$b->clear_buffer;
+
+## CREATE ##
+
+ok $b->create(), 'Create empty';
+
+ok $b->create(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ pipeline => 'foo',
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+ }
+ ),
+ 'Create';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"create":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"create":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Create actions in buffer";
+
+is $b->_buffer_size, 315, "Create actions buffer size";
+is $b->_buffer_count, 2, "Create actions buffer count";
+
+$b->clear_buffer;
+
+## CREATE DOCS##
+
+ok $b->create_docs(), 'Create_docs empty';
+
+ok $b->create_docs( { foo => 'bar' }, { foo => 'baz' } ), 'Create docs';
+
+cmp_deeply $b->_buffer,
+ [ q({"create":{}}), q({"foo":"bar"}), q({"create":{}}), q({"foo":"baz"}) ],
+ "Create docs in buffer";
+
+is $b->_buffer_size, 56, "Create docs buffer size";
+is $b->_buffer_count, 2, "Create docs buffer count";
+
+$b->clear_buffer;
+
+## DELETE ##
+ok $b->delete(), 'Delete empty';
+
+ok $b->delete(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ version => 1,
+ version_type => 'external',
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _version => 1,
+ version_type => 'external',
+ }
+ ),
+ 'Delete';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1,"_index":"foo","parent":1,"routing":1,"version":1,"version_type":"external"}}),
+ q({"delete":{"_id":2,"_index":"foo","parent":2,"routing":2,"version":1,"version_type":"external"}}),
+ ],
+ "Delete actions in buffer";
+
+is $b->_buffer_size, 194, "Delete actions buffer size";
+is $b->_buffer_count, 2, "Delete actions buffer count";
+
+$b->clear_buffer;
+
+## DELETE IDS ##
+ok $b->delete_ids(), 'Delete IDs empty';
+
+ok $b->delete_ids( 1, 2, 3 ), 'Delete IDs';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1}}), q({"delete":{"_id":2}}),
+ q({"delete":{"_id":3}}),
+ ],
+ "Delete IDs in buffer";
+
+is $b->_buffer_size, 63, "Delete IDs buffer size";
+is $b->_buffer_count, 3, "Delete IDS buffer count";
+
+$b->clear_buffer;
+
+## UPDATE ACTIONS ##
+ok $b->update(), 'Update empty';
+ok $b->update(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => ['foo'],
+ _source_excludes => ['bar'],
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ },
+ { _index => 'foo',
+ _id => 1,
+ _routing => 1,
+ _parent => 1,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => ['foo'],
+ _source_excludes => ['bar'],
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ }
+ ),
+ 'Update';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":["bar"],"_source_includes":["foo"],"detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"}),
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":["bar"],"_source_includes":["foo"],"detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"})
+ ],
+ "Update actions in buffer";
+
+is $b->_buffer_size, 710, "Update actions buffer size";
+is $b->_buffer_count, 2, "Update actions buffer count";
+
+$b->clear_buffer;
+
+done_testing;
diff --git a/t/Client_8_0/32_bulk_flush.t b/t/Client_8_0/32_bulk_flush.t
new file mode 100644
index 0000000..5a8ed9f
--- /dev/null
+++ b/t/Client_8_0/32_bulk_flush.t
@@ -0,0 +1,111 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_sync.pl" or die( $@ || $! );
+
+$es->indices->delete( index => '_all' );
+
+test_flush(
+ "max count", #
+ { max_count => 3 }, #
+ 1, 2, 0, 1, 2, 0, 1, 2, 0, 1
+);
+
+test_flush(
+ "max size", #
+ { max_size => 95 }, #
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max size > max_count",
+ { max_size => 95, max_count => 3 },
+ 1, 2, 0, 1, 2, 0, 1, 2, 0, 1
+);
+
+test_flush(
+ "max size < max_count",
+ { max_size => 95, max_count => 5 },
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max size = 0, max_count",
+ { max_size => 0, max_count => 5 },
+ 1, 2, 3, 4, 0, 1, 2, 3, 4, 0
+);
+
+test_flush(
+ "max count = 0, max_size",
+ { max_size => 95, max_count => 0 },
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max count = 0, max_size = 0",
+ { max_size => 0, max_count => 0 },
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
+);
+
+test_flush(
+ "max_count = 5, max_time = 5",
+ { max_count => 5, max_time => 5 },
+ 1, 2, 0, 1, 2, 3, 4, 0, 0, 1
+);
+
+done_testing;
+
+$es->indices->delete( index => 'test' );
+
+#===================================
+sub test_flush {
+#===================================
+ my $title = shift;
+ my $params = shift;
+ my $b = $es->bulk_helper(
+ %$params,
+ index => 'test'
+ );
+
+ my @seq = @_;
+
+ $es->indices->delete( index => 'test', ignore => 404 );
+ $es->indices->create( index => 'test' );
+ $es->cluster->health( wait_for_status => 'yellow' );
+
+ for my $i ( 10 .. 19 ) {
+
+ # sleep on 12 or 18 if max_time specified
+ if ( $params->{max_time} && ( $i == 12 || $i == 18 ) ) {
+ $b->_last_flush( time - $params->{max_time} - 1 );
+ }
+ $b->index( { id => $i, source => {} } );
+ is $b->_buffer_count, shift @seq, "$title - " . ( $i - 9 );
+ }
+ $b->flush;
+ is $b->_buffer_count, 0, "$title - final flush";
+ $es->indices->refresh;
+ is $es->count->{count}, 10, "$title - all docs indexed";
+
+}
diff --git a/t/Client_8_0/33_bulk_errors.t b/t/Client_8_0/33_bulk_errors.t
new file mode 100644
index 0000000..36fdbc7
--- /dev/null
+++ b/t/Client_8_0/33_bulk_errors.t
@@ -0,0 +1,165 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Log::Any::Adapter;
+
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_sync.pl" or die( $@ || $! );
+my $TRUE = $es->transport->serializer->decode('{"true":true}')->{true};
+
+$es->indices->delete( index => '_all' );
+
+my @Std = (
+ { id => 1, source => { count => 1 } },
+ { id => 1, source => { count => 'foo' } },
+);
+
+my ( $b, $success_count, $error_count, $custom_count, $conflict_count );
+
+## Default error handling
+$b = bulk( { index => 'test' }, @Std );
+test_flush( "Default", 0, 1, 0, 0 );
+
+## Custom error handling
+$b = bulk(
+ { index => 'test',
+ on_error => sub { $custom_count++ }
+ },
+ @Std
+);
+test_flush( "Custom error", 0, 0, 1, 0 );
+
+# Disable both
+$b = bulk(
+ { index => 'test',
+ on_conflict => undef,
+ on_error => undef
+ },
+ @Std
+);
+
+test_flush( "Both undef", 0, 0, 0, 0 );
+
+# Success
+$b = bulk(
+ { index => 'test',
+ on_success => sub { $success_count++ },
+ },
+ @Std
+);
+
+test_flush( "Success", 1, 1, 0, 0 );
+
+# cbs have correct params
+$b = bulk(
+ { index => 'test',
+ on_success => test_params(
+ 'on_success',
+ { _index => 'test',
+ _id => 1,
+ _version => 1,
+ status => 201,
+ created => $TRUE,
+ result => 'created',
+ _shards => { successful => 1, total => 2, failed => 0 },
+ _primary_term => 1,
+ _seq_no => 0
+ },
+ 0
+ ),
+ on_error => test_params(
+ 'on_error',
+ { _index => 'test',
+ _id => 1,
+ error => any(
+ re('MapperParsingException'),
+ superhashof( { type => 'mapper_parsing_exception' } )
+ ),
+ status => 400,
+ },
+ 1
+ ),
+ on_conflict => test_params(
+ 'on_conflict',
+ { _index => 'test',
+ _id => 1,
+ error => any(
+ re('version conflict'),
+ superhashof(
+ { type => 'version_conflict_engine_exception' }
+ )
+ ),
+ status => 409,
+ },
+ 2,
+ 1
+ ),
+ },
+ @Std
+);
+$b->flush;
+
+done_testing;
+
+$es->indices->delete( index => 'test' );
+
+#===================================
+sub bulk {
+#===================================
+ my $params = shift;
+ my $b = $es->bulk_helper($params);
+ $es->indices->delete( index => 'test', ignore => 404 );
+ $es->indices->create( index => 'test' );
+ $es->cluster->health( wait_for_status => 'yellow' );
+ $b->index(@_);
+ return $b;
+}
+
+#===================================
+sub test_flush {
+#===================================
+ my ( $title, $success, $default, $custom, $conflict ) = @_;
+ $success_count = $custom_count = $error_count = $conflict_count = 0;
+ {
+ local $SIG{__WARN__} = sub { $error_count++ };
+ $b->flush;
+ }
+ is $success_count, $success, "$title - success";
+ is $error_count, $default, "$title - default";
+ is $custom_count, $custom, "$title - custom";
+ is $conflict_count, $conflict, "$title - conflict";
+
+}
+
+#===================================
+sub test_params {
+#===================================
+ my ( $type, $result, $j, $version ) = @_;
+
+ return sub {
+ is $_[0], 'index', "$type - action";
+ is $_[2], $j, "$type - array index";
+ is $_[3], $version, "$type - version";
+ };
+}
diff --git a/t/Client_8_0/34_bulk_cxn_errors.t b/t/Client_8_0/34_bulk_cxn_errors.t
new file mode 100644
index 0000000..5f61fed
--- /dev/null
+++ b/t/Client_8_0/34_bulk_cxn_errors.t
@@ -0,0 +1,44 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Log::Any::Adapter;
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES} = '10.255.255.1:9200';
+$ENV{ES_SKIP_PING} = 1;
+$ENV{ES_CXN_POOL} = 'Static';
+$ENV{ES_TIMEOUT} = 1;
+
+my $es = do "es_sync.pl" or die( $@ || $! );
+
+# Check that the buffer is not cleared on a NoNodes exception
+
+my $b = $es->bulk_helper( index => 'foo', type => 'bar' );
+$b->create_docs( { foo => 'bar' } );
+
+is $b->_buffer_count, 1, "Buffer count pre-flush";
+throws_ok { $b->flush } 'Search::Elasticsearch::Error::NoNodes';
+is $b->_buffer_count, 1, "Buffer count post-flush";
+
+done_testing;
diff --git a/t/Client_8_0/40_scroll.t b/t/Client_8_0/40_scroll.t
new file mode 100644
index 0000000..4ffda93
--- /dev/null
+++ b/t/Client_8_0/40_scroll.t
@@ -0,0 +1,213 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use lib 't/lib';
+
+use strict;
+use warnings;
+
+$ENV{ES_VERSION} = '8_0';
+our $es = do "es_sync.pl" or die( $@ || $! );
+
+$es->indices->delete( index => '_all', ignore => 404 );
+
+test_scroll(
+ "No indices",
+ {},
+ total => 0,
+ max_score => 0,
+ steps => [
+ is_finished => 1,
+ next => [0],
+ refill_buffer => 0,
+ drain_buffer => [0],
+ ]
+);
+
+do "index_test_data_7.pl" or die( $@ || $! );
+
+test_scroll(
+ "Match all",
+ {},
+ total => 100,
+ max_score => 1,
+ steps => [
+ is_finished => '',
+ buffer_size => 10,
+ next => [1],
+ drain_buffer => [9],
+ refill_buffer => 10,
+ refill_buffer => 20,
+ is_finished => '',
+ next_81 => [81],
+ next_20 => [9],
+ next => [0],
+ is_finished => 1,
+ ]
+);
+
+test_scroll(
+ "Query",
+ { body => {
+ query => { term => { color => 'red' } },
+ suggest => {
+ mysuggest => { text => 'green', term => { field => 'color' } }
+ },
+ aggs => { switch => { terms => { field => 'switch' } } },
+ }
+ },
+ total => 50,
+ max_score => num( 1, 0.5 ),
+ aggs => bool(1),
+ suggest => bool(1),
+ steps => [
+ next => [1],
+ next_50 => [49],
+ is_finished => 1,
+ ]
+);
+
+test_scroll(
+ "Finish",
+ {},
+ total => 100,
+ max_score => 1,
+ steps => [
+ is_finished => '',
+ next => [1],
+ finish => 1,
+ is_finished => 1,
+ buffer_size => 0,
+ next => [0]
+
+ ]
+);
+
+my $s = $es->scroll_helper;
+my $d = $s->next;
+ok ref $d && $d->{_source}, 'next() in scalar context';
+
+{
+ # Test auto finish fork protection.
+ my $s = $es->scroll_helper( size => 5 );
+
+ my $pid = fork();
+ unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+ unless ($pid) {
+
+ # Child. Call finish check that its not finished
+ # (the call to finish did nothing).
+ $s->finish();
+ exit;
+ }
+ else {
+ # Wait for children
+ waitpid( $pid, 0 );
+ is $?, 0, "Child exited without errors";
+ }
+ ok !$s->is_finished(), "Our Scroll is not finished";
+ my $count = 0;
+ while ( $s->next ) { $count++ }
+ is $count, 100, "All documents retrieved";
+ ok $s->is_finished, "Our scroll is finished";
+}
+
+{
+ # Test Scroll usage attempt in a different process.
+ my $s = $es->scroll_helper( size => 5 );
+ my $pid = fork();
+ unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+ unless ($pid) {
+
+ # Calling this next should crash, not exiting this process with 0
+ eval {
+ while ( $s->next ) { }
+ };
+ my $err = $@;
+ exit( eval { $err->is('Illegal') && 123 } || 999 );
+ }
+ else {
+ # Wait for children
+ waitpid( $pid, 0 );
+ is $? >> 8, 123, "Child threw Illegal exception";
+ }
+}
+
+{
+ # Test valid Scroll usage after initial fork
+ my $pid = fork();
+ unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+ unless ($pid) {
+
+ my $s = $es->scroll_helper( size => 5 );
+
+ while ( $s->next ) { }
+ exit 0;
+ }
+ else {
+ # Wait for children
+ waitpid( $pid, 0 );
+ is $? , 0, "Scroll completed successfully";
+ }
+}
+
+done_testing;
+$es->indices->delete( index => 'test' );
+
+#===================================
+sub test_scroll {
+#===================================
+ my ( $title, $params, %tests ) = @_;
+
+ subtest $title => sub {
+ my $s = $es->scroll_helper($params);
+
+ is $s->total, $tests{total}, "$title - total";
+ cmp_deeply $s->max_score, $tests{max_score}, "$title - max_score";
+ cmp_deeply $s->suggest, $tests{suggest}, "$title - suggest";
+ cmp_deeply $s->aggregations, $tests{aggs}, "$title - aggs";
+ my $i = 1;
+ my @steps = @{ $tests{steps} };
+ while ( my $name = shift @steps ) {
+ my $expect = shift @steps;
+ my ( $method, $result, @p );
+ if ( $name =~ /next(?:_(\d+))?/ ) {
+ $method = 'next';
+ @p = $1;
+ }
+ else {
+ $method = $name;
+ }
+
+ if ( ref $expect eq 'ARRAY' ) {
+ my @result = $s->$method(@p);
+ $result = 0 + @result;
+ $expect = $expect->[0];
+ }
+ else {
+ $result = $s->$method(@p);
+ }
+
+ is $result, $expect, "$title - Step $i: $name";
+ $i++;
+ }
+ }
+}
+
diff --git a/t/Client_8_0/60_auth_httptiny.t b/t/Client_8_0/60_auth_httptiny.t
new file mode 100644
index 0000000..3fc5e7b
--- /dev/null
+++ b/t/Client_8_0/60_auth_httptiny.t
@@ -0,0 +1,32 @@
+# 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.
+
+use IO::Socket::SSL;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'HTTPTiny';
+our $Throws_SSL = "SSL";
+
+sub ssl_options {
+ return {
+ SSL_verify_mode => SSL_VERIFY_PEER,
+ SSL_ca_file => $_[0]
+ };
+}
+
+do "es_sync_auth.pl" or die( $@ || $! );
diff --git a/t/Client_8_0/61_auth_lwp.t b/t/Client_8_0/61_auth_lwp.t
new file mode 100644
index 0000000..d2d2ad7
--- /dev/null
+++ b/t/Client_8_0/61_auth_lwp.t
@@ -0,0 +1,31 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'LWP';
+our $Throws_SSL = "Cxn";
+
+sub ssl_options {
+ return {
+ verify_hostname => 1,
+ SSL_ca_file => $_[0]
+ };
+}
+
+do "es_sync_auth.pl" or die( $@ || $! );
diff --git a/t/Client_8_0/62_auth_netcurl.t b/t/Client_8_0/62_auth_netcurl.t
new file mode 100644
index 0000000..92977db
--- /dev/null
+++ b/t/Client_8_0/62_auth_netcurl.t
@@ -0,0 +1,32 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'NetCurl';
+use Net::Curl::Easy qw(
+ CURLOPT_CAINFO
+);
+
+our $Throws_SSL = "SSL";
+
+sub ssl_options {
+ return { CURLOPT_CAINFO() => $_[0] };
+}
+
+do "es_sync_auth.pl" or die( $@ || $! );
diff --git a/t/Client_8_0_Async/00_print_version.t b/t/Client_8_0_Async/00_print_version.t
new file mode 100644
index 0000000..5bb0bed
--- /dev/null
+++ b/t/Client_8_0_Async/00_print_version.t
@@ -0,0 +1,40 @@
+# 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.
+
+use Test::More;
+use lib 't/lib';
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+
+eval {
+ my $v = wait_for( $es->info )->{version};
+ diag "";
+ diag "";
+ diag "Testing against Search::Elasticsearch::Async v" . $v->{number};
+ for ( sort keys %$v ) {
+ diag sprintf "%-20s: %s", $_, $v->{$_};
+ }
+ diag "";
+ diag "Client: " . ref($es);
+ diag "Cxn: " . $es->transport->cxn_pool->cxn_factory->cxn_class;
+ diag "GET Body: " . $es->transport->send_get_body_as;
+ diag "";
+ pass "ES Version";
+} or fail "ES Version";
+
+done_testing;
+
diff --git a/t/Client_8_0_Async/10_live.t b/t/Client_8_0_Async/10_live.t
new file mode 100644
index 0000000..4c50199
--- /dev/null
+++ b/t/Client_8_0_Async/10_live.t
@@ -0,0 +1,58 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use AE;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+
+my $es;
+local $ENV{ES_CXN_POOL};
+
+$ENV{ES_CXN_POOL} = 'Async::Static';
+$es = do "es_async.pl" or die( $@ || $! );
+
+is wait_for( $es->info )->{tagline}, "You Know, for Search",
+ 'CxnPool::Async::Static';
+
+$ENV{ES_CXN_POOL} = 'Async::Static::NoPing';
+$es = do "es_async.pl" or die( $@ || $! );
+is wait_for( $es->info )->{tagline}, "You Know, for Search",
+ 'CxnPool::Async::Static::NoPing';
+
+$ENV{ES_CXN_POOL} = 'Async::Sniff';
+$es = do "es_async.pl" or die( $@ || $! );
+is wait_for( $es->info )->{tagline}, "You Know, for Search",
+ 'CxnPool::Async::Sniff';
+
+my ($node) = values %{
+ ( wait_for(
+ $es->transport->cxn_pool->next_cxn->then(
+ sub { shift()->sniff }
+ )
+ )
+ )[1]
+};
+ok $node->{http}{max_content_length_in_bytes}, 'Sniffs max_content length';
+
+done_testing;
+
diff --git a/t/Client_8_0_Async/20_fork_aehttp.t b/t/Client_8_0_Async/20_fork_aehttp.t
new file mode 100644
index 0000000..698c022
--- /dev/null
+++ b/t/Client_8_0_Async/20_fork_aehttp.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'AEHTTP';
+do "es_async_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_8_0_Async/21_fork_mojo.t b/t/Client_8_0_Async/21_fork_mojo.t
new file mode 100644
index 0000000..aa353fd
--- /dev/null
+++ b/t/Client_8_0_Async/21_fork_mojo.t
@@ -0,0 +1,23 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'Mojo';
+do "es_async_fork.pl" or die( $@ || $! );
+
diff --git a/t/Client_8_0_Async/30_bulk_add_action.t b/t/Client_8_0_Async/30_bulk_add_action.t
new file mode 100644
index 0000000..0b5f2bb
--- /dev/null
+++ b/t/Client_8_0_Async/30_bulk_add_action.t
@@ -0,0 +1,233 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+my ( $error, $name );
+
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+
+my $b = $es->bulk_helper(
+ on_fatal => sub {
+ like shift(), qr/$error/, $name;
+ }
+);
+
+$b->_serializer->_set_canonical;
+
+## EMPTY
+
+ok $b->add_action(), 'Empty add action';
+
+## INDEX ACTIONS ##
+
+ok $b->add_action(
+ index => {
+ index => 'foo',
+ id => 1,
+ pipeline => 'foo',
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ index => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+
+ }
+ ),
+ 'Add index actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"index":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"index":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Index actions in buffer";
+
+is $b->_buffer_size, 313, "Index actions buffer size";
+is $b->_buffer_count, 2, "Index actions buffer count";
+
+$b->clear_buffer;
+
+## CREATE ACTIONS ##
+
+ok $b->add_action(
+ create => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ pipeline => 'foo',
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ create => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+ }
+ ),
+ 'Add create actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"create":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"create":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Create actions in buffer";
+
+is $b->_buffer_size, 315, "Create actions buffer size";
+is $b->_buffer_count, 2, "Create actions buffer count";
+
+$b->clear_buffer;
+
+## DELETE ACTIONS ##
+
+ok $b->add_action(
+ delete => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ version => 1,
+ version_type => 'external',
+ },
+ delete => {
+ _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _version => 1,
+ version_type => 'external',
+ }
+ ),
+ 'Add delete actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1,"_index":"foo","parent":1,"routing":1,"version":1,"version_type":"external"}}),
+ q({"delete":{"_id":2,"_index":"foo","parent":2,"routing":2,"version":1,"version_type":"external"}}),
+ ],
+ "Delete actions in buffer";
+
+is $b->_buffer_size, 194, "Delete actions buffer size";
+is $b->_buffer_count, 2, "Delete actions buffer count";
+
+$b->clear_buffer;
+
+## UPDATE ACTIONS ##
+
+ok $b->add_action(
+ update => {
+ index => 'foo',
+ id => 1,
+ routing => 1,
+ _source => 'true',
+ _source_includes => 'foo',
+ _source_excludes => 'bar',
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ },
+ update => {
+ _index => 'foo',
+ _id => 1,
+ _routing => 1,
+ _source => 'true',
+ _source_includes => 'foo',
+ _source_excludes => 'bar',
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ }
+ ),
+ 'Add update actions';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"update":{"_id":1,"_index":"foo","routing":1}}),
+ q({"_source":"true","_source_excludes":"bar","_source_includes":"foo","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"}),
+ q({"update":{"_id":1,"_index":"foo","routing":1}}),
+ q({"_source":"true","_source_excludes":"bar","_source_includes":"foo","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"})
+ ],
+ "Update actions in buffer";
+
+is $b->_buffer_size, 486, "Update actions buffer size";
+is $b->_buffer_count, 2, "Update actions buffer count";
+
+$b->clear_buffer;
+
+## ERRORS ##
+$error = "Unrecognised action";
+$name = 'Bad action';
+$b->add_action( 'foo' => {} );
+
+$error = "Missing <params>";
+$name = 'Missing params';
+$b->add_action( 'index', 'bar' );
+
+$error = "Missing .*<index>";
+$name = 'Missing index';
+$b->add_action( index => { type => 't' } );
+
+$error = "Missing .*<source>";
+$name = 'Missing source';
+$b->add_action( index => { index => 'i', type => 't' } );
+
+$error = "Unknown params";
+$name = 'Unknown params';
+$b->add_action(
+ index => { index => 'i', type => 't', source => {}, foo => 1 } );
+
+done_testing;
diff --git a/t/Client_8_0_Async/31_bulk_helpers.t b/t/Client_8_0_Async/31_bulk_helpers.t
new file mode 100644
index 0000000..8423717
--- /dev/null
+++ b/t/Client_8_0_Async/31_bulk_helpers.t
@@ -0,0 +1,245 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use strict;
+use warnings;
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+
+my $b = $es->bulk_helper(
+ index => 'i',
+ type => 't'
+);
+my $s = $b->_serializer;
+$s->_set_canonical;
+
+## INDEX ##
+
+ok $b->index(), 'Empty index';
+
+ok $b->index(
+ { index => 'foo',
+ id => 1,
+ pipeline => 'foo',
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+
+ }
+ ),
+ 'Index';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"index":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"index":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Index in buffer";
+
+is $b->_buffer_size, 313, "Index buffer size";
+is $b->_buffer_count, 2, "Index buffer count";
+
+$b->clear_buffer;
+
+## CREATE ##
+
+ok $b->create(), 'Create empty';
+
+ok $b->create(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ pipeline => 'foo',
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ source => { foo => 'bar' },
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ source => { foo => 'bar' },
+ }
+ ),
+ 'Create';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"create":{"_id":1,"_index":"foo","parent":1,"pipeline":"foo","routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"}),
+ q({"create":{"_id":2,"_index":"foo","parent":2,"routing":2,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"foo":"bar"})
+ ],
+ "Create actions in buffer";
+
+is $b->_buffer_size, 315, "Create actions buffer size";
+is $b->_buffer_count, 2, "Create actions buffer count";
+
+$b->clear_buffer;
+
+## CREATE DOCS##
+
+ok $b->create_docs(), 'Create_docs empty';
+
+ok $b->create_docs( { foo => 'bar' }, { foo => 'baz' } ), 'Create docs';
+
+cmp_deeply $b->_buffer,
+ [ q({"create":{}}), q({"foo":"bar"}), q({"create":{}}), q({"foo":"baz"}) ],
+ "Create docs in buffer";
+
+is $b->_buffer_size, 56, "Create docs buffer size";
+is $b->_buffer_count, 2, "Create docs buffer count";
+
+$b->clear_buffer;
+
+## DELETE ##
+ok $b->delete(), 'Delete empty';
+
+ok $b->delete(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ version => 1,
+ version_type => 'external',
+ },
+ { _index => 'foo',
+ _id => 2,
+ _routing => 2,
+ _parent => 2,
+ _version => 1,
+ version_type => 'external',
+ }
+ ),
+ 'Delete';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1,"_index":"foo","parent":1,"routing":1,"version":1,"version_type":"external"}}),
+ q({"delete":{"_id":2,"_index":"foo","parent":2,"routing":2,"version":1,"version_type":"external"}}),
+ ],
+ "Delete actions in buffer";
+
+is $b->_buffer_size, 194, "Delete actions buffer size";
+is $b->_buffer_count, 2, "Delete actions buffer count";
+
+$b->clear_buffer;
+
+## DELETE IDS ##
+ok $b->delete_ids(), 'Delete IDs empty';
+
+ok $b->delete_ids( 1, 2, 3 ), 'Delete IDs';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"delete":{"_id":1}}), q({"delete":{"_id":2}}),
+ q({"delete":{"_id":3}}),
+ ],
+ "Delete IDs in buffer";
+
+is $b->_buffer_size, 63, "Delete IDs buffer size";
+is $b->_buffer_count, 3, "Delete IDS buffer count";
+
+$b->clear_buffer;
+
+## UPDATE ACTIONS ##
+ok $b->update(), 'Update empty';
+ok $b->update(
+ { index => 'foo',
+ id => 1,
+ routing => 1,
+ parent => 1,
+ timestamp => 1380019061000,
+ ttl => '10m',
+ version => 1,
+ version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => 'foo',
+ _source_excludes => 'bar',
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ },
+ { _index => 'foo',
+ _id => 1,
+ _routing => 1,
+ _parent => 1,
+ _timestamp => 1380019061000,
+ _ttl => '10m',
+ _version => 1,
+ _version_type => 'external',
+ detect_noop => 'true',
+ _source => 'true',
+ _source_includes => 'foo',
+ _source_excludes => 'bar',
+ doc => { foo => 'bar' },
+ doc_as_upsert => 1,
+ fields => ["*"],
+ script => 'ctx._source+=1',
+ scripted_upsert => 'true',
+ retry_on_conflict => 3,
+ }
+ ),
+ 'Update';
+
+cmp_deeply $b->_buffer,
+ [
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":"bar","_source_includes":"foo","detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"}),
+ q({"update":{"_id":1,"_index":"foo","parent":1,"routing":1,"timestamp":1380019061000,"ttl":"10m","version":1,"version_type":"external"}}),
+ q({"_source":"true","_source_excludes":"bar","_source_includes":"foo","detect_noop":"true","doc":{"foo":"bar"},"doc_as_upsert":1,"fields":["*"],"retry_on_conflict":3,"script":"ctx._source+=1","scripted_upsert":"true"})
+ ],
+ "Update actions in buffer";
+
+is $b->_buffer_size, 702, "Update actions buffer size";
+is $b->_buffer_count, 2, "Update actions buffer count";
+
+$b->clear_buffer;
+
+done_testing;
diff --git a/t/Client_8_0_Async/32_bulk_flush.t b/t/Client_8_0_Async/32_bulk_flush.t
new file mode 100644
index 0000000..a1fda97
--- /dev/null
+++ b/t/Client_8_0_Async/32_bulk_flush.t
@@ -0,0 +1,147 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use strict;
+use warnings;
+use lib 't/lib';
+use AE;
+use Promises qw(deferred);
+
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+
+wait_for( $es->indices->delete( index => '_all' ) );
+
+test_flush(
+ "max count", #
+ { max_count => 3 }, #
+ 1, 2, 0, 1, 2, 0, 1, 2, 0, 1
+);
+
+test_flush(
+ "max size", #
+ { max_size => 95 }, #
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max size > max_count",
+ { max_size => 95, max_count => 3 },
+ 1, 2, 0, 1, 2, 0, 1, 2, 0, 1
+);
+
+test_flush(
+ "max size < max_count",
+ { max_size => 95, max_count => 5 },
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max size = 0, max_count",
+ { max_size => 0, max_count => 5 },
+ 1, 2, 3, 4, 0, 1, 2, 3, 4, 0
+);
+
+test_flush(
+ "max count = 0, max_size",
+ { max_size => 95, max_count => 0 },
+ 1, 2, 3, 0, 1, 2, 3, 0, 1, 2
+);
+
+test_flush(
+ "max count = 0, max_size = 0",
+ { max_size => 0, max_count => 0 },
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
+);
+
+test_flush(
+ "max_count = 5, max_time = 5",
+ { max_count => 5, max_time => 5 },
+ 1, 2, 0, 1, 2, 3, 4, 0, 0, 1
+);
+
+done_testing;
+
+wait_for( $es->indices->delete( index => 'test' ) );
+
+#===================================
+sub test_flush {
+#===================================
+ my $title = shift;
+ my $params = shift;
+ my $b = $es->bulk_helper(
+ %$params,
+ index => 'test'
+ );
+
+ my @seq = @_;
+ my $cv = AE::cv;
+
+ my $i = 10;
+ my $loop;
+
+ my $index_doc = sub {
+ $b->index( { id => $i, source => {} } );
+ };
+
+ my $check_buffer = sub {
+ is $b->_buffer_count, shift @seq, "$title - " . ( $i - 9 );
+ $i++;
+ };
+
+ my $d = deferred;
+ my $w;
+
+ $loop = sub {
+ if ( $i == 20 ) {
+ return $b->flush->then( sub { $d->resolve } );
+ }
+
+ # sleep on 12 or 18 if max_time specified
+ if ( $params->{max_time} && ( $i == 12 || $i == 18 ) ) {
+ $b->_last_flush( time - $params->{max_time} - 1 );
+ }
+ $index_doc->()->then($check_buffer)->then($loop);
+ };
+
+ $es->indices->delete( index => 'test', ignore => 404 )
+ ->then( sub { $es->indices->create( index => 'test' ) } )
+ ->then( sub { $es->cluster->health( wait_for_status => 'yellow' ) } )
+ ->then($loop);
+
+ $d->promise->then(
+ sub {
+ is $b->_buffer_count, 0, "$title - final flush";
+ $es->indices->refresh;
+ }
+ )->then(
+ sub {
+ $es->count;
+ }
+ )->then(
+ sub {
+ is shift()->{count}, 10, "$title - all docs indexed";
+ }
+ )->then(
+ sub {
+ $cv->send;
+ }
+ )->catch( sub { $cv->croak(@_) } );
+ $cv->recv;
+}
diff --git a/t/Client_8_0_Async/33_bulk_errors.t b/t/Client_8_0_Async/33_bulk_errors.t
new file mode 100644
index 0000000..2bf269e
--- /dev/null
+++ b/t/Client_8_0_Async/33_bulk_errors.t
@@ -0,0 +1,212 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use AE;
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Log::Any::Adapter;
+
+$ENV{ES_VERSION} = '8_0';
+my $es = do "es_async.pl" or die( $@ || $! );
+my $TRUE = $es->transport->serializer->decode('{"true":true}')->{true};
+
+my $cv = AE::cv;
+
+wait_for( $es->indices->delete( index => '_all' ) );
+
+my @Std = (
+ { id => 1, source => { count => 1 } },
+ { id => 1, source => { count => 'foo' } },
+ { id => 1, source => {} },
+);
+
+my ( $b, $error, $success_count, $error_count, $custom_count, $conflict_count );
+
+## Default error handling
+$b = bulk( { index => 'test'}, @Std );
+test_flush( "Default", 0, 1, 0, 0 );
+
+## Custom error handling
+$b = bulk(
+ { index => 'test',
+ on_error => sub { $custom_count++ }
+ },
+ @Std
+);
+test_flush( "Custom error", 0, 0, 1, 0 );
+
+# Conflict errors
+$b = bulk(
+ { index => 'test',
+ on_conflict => sub { $conflict_count++ }
+ },
+ @Std
+);
+test_flush( "Conflict error", 0, 1, 0, 0 );
+
+# Both error handling
+$b = bulk(
+ { index => 'test',
+ on_conflict => sub { $conflict_count++ },
+ on_error => sub { $custom_count++ }
+ },
+ @Std
+);
+
+test_flush( "Conflict and custom", 0, 0, 1, 0 );
+
+# Conflict disable error
+$b = bulk(
+ { index => 'test',
+ on_conflict => sub { $conflict_count++ },
+ on_error => undef
+ },
+ @Std
+);
+
+test_flush( "Conflict, error undef", 0, 0, 0, 0 );
+
+# Disable both
+$b = bulk(
+ { index => 'test',
+ on_conflict => undef,
+ on_error => undef
+ },
+ @Std
+);
+
+test_flush( "Both undef", 0, 0, 0, 0 );
+
+# Success
+$b = bulk(
+ { index => 'test',
+ on_success => sub { $success_count++ },
+ },
+ @Std
+);
+
+test_flush( "Success", 2, 1, 0, 0 );
+
+# cbs have correct params
+$b = bulk(
+ { index => 'test',
+ on_success => test_params(
+ 'on_success',
+ { _index => 'test',
+ _type => '_doc',
+ _id => 1,
+ _version => 1,
+ created => $TRUE,
+ _shards => { successful => 1, total => 2, failed => 0 },
+ _primary_term => 1,
+ _seq_no => 0,
+ },
+ 0
+ ),
+ on_error => test_params(
+ 'on_error',
+ { _index => 'test',
+ _type => '_doc',
+ _id => 1,
+ error => any(
+ re('MapperParsingException'),
+ superhashof( { type => 'mapper_parsing_exception' } )
+ ),
+ status => 400,
+ },
+ 1
+ ),
+ on_conflict => test_params(
+ 'on_conflict',
+ { _index => 'test',
+ _type => '_doc',
+ _id => 1,
+ error => any(
+ re('version conflict'),
+ superhashof(
+ { type => 'version_conflict_engine_exception' }
+ )
+ ),
+ status => 409,
+ },
+ 2, 1
+ ),
+ },
+ @Std
+);
+wait_for( $b->flush );
+
+done_testing;
+
+wait_for( $es->indices->delete( index => '_all' ) );
+
+#===================================
+sub bulk {
+#===================================
+ my ( $params, @docs ) = @_;
+ my $b = $es->bulk_helper(
+ on_fatal => sub { $error = shift(); $error_count++ },
+ %$params,
+ );
+
+ $error = '';
+
+ wait_for(
+ $es->indices->delete( index => 'test', ignore => 404 ) #
+ ->then( sub { $es->indices->create( index => 'test' ) } ) #
+ ->then(
+ sub { $es->cluster->health( wait_for_status => 'yellow' ) }
+ ) #
+ ->then( sub { $b->index(@docs) } )
+ );
+ return $b;
+}
+
+#===================================
+sub test_flush {
+#===================================
+ my ( $title, $success, $default, $custom, $conflict ) = @_;
+ $success_count = $custom_count = $error_count = $conflict_count = 0;
+ {
+ local $SIG{__WARN__} = sub { $error_count++ };
+ wait_for( $b->flush );
+ }
+ is $success_count, $success, "$title - success";
+ is $error_count, $default, "$title - default";
+ is $custom_count, $custom, "$title - custom";
+ is $conflict_count, $conflict, "$title - conflict";
+
+}
+
+#===================================
+sub test_params {
+#===================================
+ my ( $type, $result, $j, $version ) = @_;
+
+ return sub {
+ is $_[0], 'index', "$type - action";
+ cmp_deeply subhashof($result), $_[1], "$type - result";
+ is $_[2], $j, "$type - array index";
+ is $_[3], $version, "$type - version";
+ };
+}
+
diff --git a/t/Client_8_0_Async/34_bulk_cxn_errors.t b/t/Client_8_0_Async/34_bulk_cxn_errors.t
new file mode 100644
index 0000000..190b31c
--- /dev/null
+++ b/t/Client_8_0_Async/34_bulk_cxn_errors.t
@@ -0,0 +1,53 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use AE;
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Log::Any::Adapter;
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES} = '10.255.255.1:9200';
+$ENV{ES_SKIP_PING} = 1;
+$ENV{ES_CXN_POOL} = 'Async::Static';
+
+my $es = do "es_async.pl" or die( $@ || $! );
+my $error;
+my $b = $es->bulk_helper( index => 'foo', type => 'bar' );
+$b->create_docs( { foo => 'bar' } );
+
+# Check that the buffer is not cleared on a NoNodes exception
+
+is $b->_buffer_count, 1, "Buffer count pre-flush";
+
+wait_for(
+ $b->flush->catch(
+ sub {
+ my $error = shift;
+ isa_ok $error, 'Search::Elasticsearch::Error::NoNodes';
+ }
+ )
+);
+
+is $b->_buffer_count, 1, "Buffer count post-flush";
+
+done_testing;
diff --git a/t/Client_8_0_Async/40_scroll.t b/t/Client_8_0_Async/40_scroll.t
new file mode 100644
index 0000000..6e7bdcc
--- /dev/null
+++ b/t/Client_8_0_Async/40_scroll.t
@@ -0,0 +1,205 @@
+# 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.
+
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use lib 't/lib';
+
+use strict;
+use warnings;
+
+our ( $s, $total_seen, $max_seen );
+$ENV{ES_VERSION} = '8_0';
+our $es = do "es_async.pl" or die( $@ || $! );
+
+wait_for( $es->indices->delete( index => '_all', ignore => 404 ) );
+
+test_scroll(
+ "No indices",
+ { on_results => \&on_results },
+ total => 0,
+ max_score => 0,
+ total_seen => 0,
+ max_seen => 0,
+);
+
+do "index_test_data_7.pl" or die( $@ || $! );
+
+test_scroll(
+ "Match all - on_result",
+ { on_result => \&on_results, size => 10 },
+ total => 100,
+ max_score => 1,
+ total_seen => 100,
+ max_seen => 1
+);
+
+test_scroll(
+ "Match all - on_results",
+ { on_results => \&on_results, size => 10 },
+ total => 100,
+ max_score => 1,
+ total_seen => 100,
+ max_seen => 10
+);
+
+test_scroll(
+ "Query",
+ { body => {
+ query => { term => { color => 'red' } },
+ suggest => {
+ mysuggest => { text => 'green', term => { field => 'color' } }
+ },
+ aggs => { switch => { terms => { field => 'switch' } } },
+ },
+ size => 10,
+ on_results => \&on_results
+ },
+ total => 50,
+ max_score => num( 1.0, 0.5 ),
+ aggs => bool(1),
+ suggest => bool(1),
+ total_seen => 50,
+ max_seen => 10
+);
+
+test_scroll(
+ "Finish",
+ { on_results => sub {
+ on_results(@_);
+ $s->finish if $total_seen == 30;
+ },
+ size => 10
+ },
+ total => 100,
+ max_score => 1,
+ total_seen => 30,
+ max_seen => 10
+);
+
+{
+ # Test auto finish fork protection.
+ my $count = 0;
+ my $s = $es->scroll_helper( size => 5, on_result => sub { $count++ } );
+
+ my $pid = fork();
+ unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+ unless ($pid) {
+
+ # Child. Call finish check that its not finished
+ # (the call to finish did nothing).
+ wait_for( $s->finish() );
+ exit 0;
+ }
+ else {
+ # Wait for children
+ waitpid( $pid, 0 );
+ is $?, 0, "Child exited without errors";
+ }
+ ok !$s->is_finished(), "Our Scroll is not finished";
+ wait_for( $s->start );
+ is $count, 100, "All documents retrieved";
+ ok $s->is_finished, "Our scroll is finished";
+}
+
+# {
+# # Test Scroll usage attempt in a different process.
+# my $count = 0;
+# my $s = $es->scroll_helper(
+# size => 5,
+# on_result => sub { $count++ },
+# on_error => sub { die @_ }
+# );
+
+# my $pid = fork();
+# unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+# unless ($pid) {
+
+# eval { wait_for( $s->start ) };
+# my $err = $@;
+# exit( eval { $err->is('Illegal') && 123 } || 999 );
+# }
+# else {
+# # Wait for children
+# waitpid( $pid, 0 );
+# is $? >> 8, 123, "Child threw Illegal exception";
+# }
+# }
+
+# {
+# # Test valid Scroll usage after initial fork
+# my $pid = fork();
+# unless ( defined($pid) ) { die "Cannot fork. Lack of resources?"; }
+# unless ($pid) {
+
+# my $count = 0;
+# my $s = $es->scroll_helper(
+# size => 5,
+# on_result => sub { $count++ },
+# on_error => sub { die @_ }
+# );
+
+# wait_for( $s->start );
+# exit 0;
+# }
+# else {
+# # Wait for children
+# waitpid( $pid, 0 );
+# is $? , 0, "Scroll completed successfully";
+# }
+# }
+
+done_testing;
+
+wait_for( $es->indices->delete( index => 'test' ) );
+
+#===================================
+sub test_scroll {
+#===================================
+ my ( $title, $params, %tests ) = @_;
+ $max_seen = $total_seen = 0;
+ subtest $title => sub {
+ $s = $es->scroll_helper(
+ on_start => sub { test_start( $title, \%tests, @_ ) },
+ %$params
+ );
+ wait_for( $s->start );
+
+ is $total_seen, $tests{total_seen}, "$title - total seen";
+ is $max_seen, $tests{max_seen}, "$title - max seen";
+
+ };
+}
+
+#===================================
+sub test_start {
+#===================================
+ my ( $title, $tests, $s ) = @_;
+ is $s->total, $tests->{total}, "$title - total";
+ cmp_deeply $s->max_score, $tests->{max_score}, "$title - max_score";
+ cmp_deeply $s->suggest, $tests->{suggest}, "$title - suggest";
+ cmp_deeply $s->aggregations, $tests->{aggs}, "$title - aggs";
+
+}
+
+#===================================
+sub on_results {
+#===================================
+ $max_seen = @_ if @_ > $max_seen;
+ $total_seen += @_;
+}
diff --git a/t/Client_8_0_Async/60_auth_aehttp.t b/t/Client_8_0_Async/60_auth_aehttp.t
new file mode 100644
index 0000000..93f6bd4
--- /dev/null
+++ b/t/Client_8_0_Async/60_auth_aehttp.t
@@ -0,0 +1,31 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'AEHTTP';
+
+sub ssl_options {
+ return {
+ verify => 1,
+ verify_peername => 'https',
+ ca_file => $_[0]
+ };
+}
+
+do "es_async_auth.pl" or die( $@ || $! );
diff --git a/t/Client_8_0_Async/61_auth_mojo.t b/t/Client_8_0_Async/61_auth_mojo.t
new file mode 100644
index 0000000..2756149
--- /dev/null
+++ b/t/Client_8_0_Async/61_auth_mojo.t
@@ -0,0 +1,27 @@
+# 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.
+
+use lib 't/lib';
+
+$ENV{ES_VERSION} = '8_0';
+$ENV{ES_CXN} = 'Mojo';
+
+sub ssl_options {
+ return { ca => $_[0] };
+}
+
+do "es_async_auth.pl" or die( $@ || $! );
diff --git a/t/author-eol.t b/t/author-eol.t
deleted file mode 100644
index e277938..0000000
--- a/t/author-eol.t
+++ /dev/null
@@ -1,61 +0,0 @@
-
-BEGIN {
- unless ($ENV{AUTHOR_TESTING}) {
- print qq{1..0 # SKIP these tests are for testing by the author\n};
- exit
- }
-}
-
-use strict;
-use warnings;
-
-# this test was generated with Dist::Zilla::Plugin::Test::EOL 0.19
-
-use Test::More 0.88;
-use Test::EOL;
-
-my @files = (
- 'lib/Search/Elasticsearch/Client/2_0.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Bulk.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Cat.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Cluster.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Indices.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Nodes.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Snapshot.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Tasks.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Role/API.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Role/Bulk.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Role/Scroll.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Scroll.pm',
- 'lib/Search/Elasticsearch/Client/2_0/TestServer.pm',
- 't/Client_2_0/00_print_version.t',
- 't/Client_2_0/10_live.t',
- 't/Client_2_0/15_conflict.t',
- 't/Client_2_0/20_fork_httptiny.t',
- 't/Client_2_0/21_fork_lwp.t',
- 't/Client_2_0/22_fork_hijk.t',
- 't/Client_2_0/30_bulk_add_action.t',
- 't/Client_2_0/31_bulk_helpers.t',
- 't/Client_2_0/32_bulk_flush.t',
- 't/Client_2_0/33_bulk_errors.t',
- 't/Client_2_0/34_bulk_cxn_errors.t',
- 't/Client_2_0/40_scroll.t',
- 't/Client_2_0/50_reindex.t',
- 't/Client_2_0/60_auth_httptiny.t',
- 't/Client_2_0/61_auth_lwp.t',
- 't/author-eol.t',
- 't/author-no-tabs.t',
- 't/author-pod-syntax.t',
- 't/lib/LogCallback.pl',
- 't/lib/MockCxn.pm',
- 't/lib/bad_cacert.pem',
- 't/lib/default_cxn.pl',
- 't/lib/es_sync.pl',
- 't/lib/es_sync_auth.pl',
- 't/lib/es_sync_fork.pl',
- 't/lib/index_test_data.pl'
-);
-
-eol_unix_ok($_, { trailing_whitespace => 1 }) foreach @files;
-done_testing;
diff --git a/t/author-no-tabs.t b/t/author-no-tabs.t
deleted file mode 100644
index a4d35b7..0000000
--- a/t/author-no-tabs.t
+++ /dev/null
@@ -1,61 +0,0 @@
-
-BEGIN {
- unless ($ENV{AUTHOR_TESTING}) {
- print qq{1..0 # SKIP these tests are for testing by the author\n};
- exit
- }
-}
-
-use strict;
-use warnings;
-
-# this test was generated with Dist::Zilla::Plugin::Test::NoTabs 0.15
-
-use Test::More 0.88;
-use Test::NoTabs;
-
-my @files = (
- 'lib/Search/Elasticsearch/Client/2_0.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Bulk.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Cat.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Cluster.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Indices.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Nodes.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Snapshot.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Direct/Tasks.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Role/API.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Role/Bulk.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Role/Scroll.pm',
- 'lib/Search/Elasticsearch/Client/2_0/Scroll.pm',
- 'lib/Search/Elasticsearch/Client/2_0/TestServer.pm',
- 't/Client_2_0/00_print_version.t',
- 't/Client_2_0/10_live.t',
- 't/Client_2_0/15_conflict.t',
- 't/Client_2_0/20_fork_httptiny.t',
- 't/Client_2_0/21_fork_lwp.t',
- 't/Client_2_0/22_fork_hijk.t',
- 't/Client_2_0/30_bulk_add_action.t',
- 't/Client_2_0/31_bulk_helpers.t',
- 't/Client_2_0/32_bulk_flush.t',
- 't/Client_2_0/33_bulk_errors.t',
- 't/Client_2_0/34_bulk_cxn_errors.t',
- 't/Client_2_0/40_scroll.t',
- 't/Client_2_0/50_reindex.t',
- 't/Client_2_0/60_auth_httptiny.t',
- 't/Client_2_0/61_auth_lwp.t',
- 't/author-eol.t',
- 't/author-no-tabs.t',
- 't/author-pod-syntax.t',
- 't/lib/LogCallback.pl',
- 't/lib/MockCxn.pm',
- 't/lib/bad_cacert.pem',
- 't/lib/default_cxn.pl',
- 't/lib/es_sync.pl',
- 't/lib/es_sync_auth.pl',
- 't/lib/es_sync_fork.pl',
- 't/lib/index_test_data.pl'
-);
-
-notabs_ok($_) foreach @files;
-done_testing;
diff --git a/t/author-pod-syntax.t b/t/author-pod-syntax.t
deleted file mode 100644
index 2233af0..0000000
--- a/t/author-pod-syntax.t
+++ /dev/null
@@ -1,15 +0,0 @@
-#!perl
-
-BEGIN {
- unless ($ENV{AUTHOR_TESTING}) {
- print qq{1..0 # SKIP these tests are for testing by the author\n};
- exit
- }
-}
-
-# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests.
-use strict; use warnings;
-use Test::More;
-use Test::Pod 1.41;
-
-all_pod_files_ok();
diff --git a/t/lib/LogCallback.pl b/t/lib/LogCallback.pl
index 151a79b..09728bd 100644
--- a/t/lib/LogCallback.pl
+++ b/t/lib/LogCallback.pl
@@ -1,3 +1,20 @@
+# 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.
+
use Log::Any::Adapter::Callback 0.09;
use Log::Any::Adapter;
diff --git a/t/lib/MockAsyncCxn.pm b/t/lib/MockAsyncCxn.pm
new file mode 100644
index 0000000..4d6b580
--- /dev/null
+++ b/t/lib/MockAsyncCxn.pm
@@ -0,0 +1,166 @@
+# 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 MockAsyncCxn;
+
+use strict;
+use warnings;
+use Search::Elasticsearch::Role::Cxn qw(PRODUCT_CHECK_HEADER PRODUCT_CHECK_VALUE);
+
+our $PRODUCT_CHECK_VALUE = $Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_VALUE;
+our $PRODUCT_CHECK_HEADER = $Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_HEADER;
+our $VERSION = $Search::Elasticsearch::VERSION;
+
+use Promises qw(deferred);
+use Data::Dumper;
+use Moo;
+with 'Search::Elasticsearch::Role::Cxn::Async';
+with 'Search::Elasticsearch::Role::Cxn',
+ 'Search::Elasticsearch::Role::Is_Async';
+
+use Sub::Exporter -setup => {
+ exports => [ qw(
+ mock_static_client
+ mock_sniff_client
+ mock_noping_client
+ )
+ ]
+};
+
+our $i = 0;
+
+has 'mock_responses' => ( is => 'rw', required => 1 );
+has 'marked_live' => ( is => 'rw', default => sub {0} );
+has 'node_num' => ( is => 'ro', default => sub { ++$i } );
+
+#===================================
+sub BUILD {
+#===================================
+ my $self = shift;
+ $self->logger->debugf( "[%s-%s] CREATED", $self->node_num, $self->host );
+}
+
+#===================================
+sub error_from_text { return $_[2] }
+#===================================
+
+#===================================
+sub perform_request {
+#===================================
+ my $self = shift;
+ my $params = shift;
+
+ my $d = deferred;
+
+ eval {
+ my $response = shift @{ $self->mock_responses }
+ or die "Mock responses exhausted";
+
+ if ( my $node = $response->{node} ) {
+ die "Mock response handled by wrong node ["
+ . $self->node_num . "]: "
+ . Dumper($response)
+ unless $node eq $self->node_num;
+ }
+
+ my $log_msg;
+
+ # Sniff request
+ if ( my $nodes = $response->{sniff} ) {
+ $log_msg = "SNIFF: [" . ( join ", ", @$nodes ) . "]";
+ $response->{code} ||= 200;
+ my $i = 1;
+ unless ( $response->{error} ) {
+ $response->{content} = $self->serializer->encode(
+ { nodes => {
+ map {
+ 'node_'
+ . $i++ => { http_address => "inet[/$_]" }
+ } @$nodes
+ }
+ }
+ );
+ }
+ }
+
+ # Normal request
+ elsif ( $response->{code} ) {
+ $log_msg
+ = "REQUEST: " . ( $response->{error} || $response->{code} );
+ }
+
+ # Ping request
+ else {
+ $log_msg = "PING: " . ( $response->{ping} ? 'OK' : 'NOT_OK' );
+ $response
+ = $response->{ping}
+ ? { code => 200 }
+ : { code => 500, error => 'Cxn' };
+ }
+
+ $self->logger->debugf( "[%s-%s] %s", $self->node_num, $self->host,
+ $log_msg );
+
+ $d->resolve(
+ $self->process_response(
+ $params, # request
+ $response->{code}, # code
+ $response->{error}, # msg
+ $response->{content}, # body
+ {
+ 'content-type' => 'application/json',
+ $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE
+ }
+ )
+ );
+ 1;
+ } || do { $d->reject( $@ || 'Unknown error' ) };
+ return $d->promise;
+}
+
+#### EXPORTS ###
+
+my $trace
+ = !$ENV{TRACE} ? undef
+ : $ENV{TRACE} eq '1' ? 'Stderr'
+ : [ 'File', $ENV{TRACE} ];
+
+#===================================
+sub mock_static_client { _mock_client( 'Async::Static', @_ ) }
+sub mock_sniff_client { _mock_client( 'Async::Sniff', @_ ) }
+sub mock_noping_client { _mock_client( 'Async::Static::NoPing', @_ ) }
+#===================================
+
+#===================================
+sub _mock_client {
+#===================================
+ my $pool = shift;
+ my $params = shift;
+ $i = 0;
+ return Search::Elasticsearch::Async->new(
+ cxn => '+MockAsyncCxn',
+ transport => '+MockAsyncTransport',
+ cxn_pool => $pool,
+ mock_responses => \@_,
+ dead_timeout => 500,
+ randomize_cxns => 0,
+ log_to => $trace,
+ %$params,
+ )->transport;
+}
+
+1
diff --git a/t/lib/MockAsyncTransport.pm b/t/lib/MockAsyncTransport.pm
new file mode 100644
index 0000000..d44039b
--- /dev/null
+++ b/t/lib/MockAsyncTransport.pm
@@ -0,0 +1,25 @@
+package MockAsyncTransport;
+
+use strict;
+use warnings;
+
+our $VERSION = $Search::Elasticsearch::VERSION;
+
+use AE;
+use Moo;
+extends 'Search::Elasticsearch::Transport::Async';
+
+our $w;
+#===================================
+sub perform_sync_request {
+#===================================
+ my $self = shift;
+ my $cv = AE::cv;
+ $w = AE::timer( 1, 0,
+ sub { $cv->croak('Response timed out'); undef $w } );
+ my $promise = $self->perform_request(@_);
+ $promise->then( sub { $cv->send(@_) }, sub { $cv->croak(@_) } );
+ $cv->recv;
+}
+
+1
diff --git a/t/lib/MockCxn.pm b/t/lib/MockCxn.pm
index 271c384..cc70e7b 100644
--- a/t/lib/MockCxn.pm
+++ b/t/lib/MockCxn.pm
@@ -1,8 +1,28 @@
+# 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 MockCxn;
use strict;
use warnings;
+use Search::Elasticsearch::Role::Cxn qw(PRODUCT_CHECK_HEADER PRODUCT_CHECK_VALUE);
+our $PRODUCT_CHECK_VALUE = $Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_VALUE;
+our $PRODUCT_CHECK_HEADER = $Search::Elasticsearch::Role::Cxn::PRODUCT_CHECK_HEADER;
our $VERSION = $Search::Elasticsearch::VERSION;
use Data::Dumper;
@@ -93,7 +113,10 @@ sub perform_request {
$response->{code}, # code
$response->{error}, # msg
$response->{content}, # body
- { 'content-type' => 'application/json' }
+ {
+ 'content-type' => 'application/json',
+ $PRODUCT_CHECK_HEADER => $PRODUCT_CHECK_VALUE
+ }
);
}
diff --git a/t/lib/default_async_cxn.pl b/t/lib/default_async_cxn.pl
new file mode 100644
index 0000000..30065a0
--- /dev/null
+++ b/t/lib/default_async_cxn.pl
@@ -0,0 +1,18 @@
+# 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.
+
+return 'AEHTTP';
diff --git a/t/lib/default_cxn.pl b/t/lib/default_cxn.pl
index 2a2431f..498e07c 100644
--- a/t/lib/default_cxn.pl
+++ b/t/lib/default_cxn.pl
@@ -1 +1,18 @@
+# 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.
+
return 'HTTPTiny';
diff --git a/t/lib/es_async.pl b/t/lib/es_async.pl
new file mode 100644
index 0000000..604b87e
--- /dev/null
+++ b/t/lib/es_async.pl
@@ -0,0 +1,124 @@
+# 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.
+
+#!perl -d
+use EV;
+use AE;
+
+use Promises backend => ['EV'];
+use Search::Elasticsearch::Async;
+use Test::More;
+use strict;
+use warnings;
+
+my $trace
+ = !$ENV{TRACE} ? undef
+ : $ENV{TRACE} eq '1' ? 'Stderr'
+ : [ 'File', $ENV{TRACE} ];
+
+unless ($ENV{CLIENT_VER}) {
+ plan skip_all => 'No $ENV{CLIENT_VER} specified';
+ exit;
+}
+unless ($ENV{ES}) {
+ plan skip_all => 'No Elasticsearch test node available';
+ exit;
+}
+
+my $cv = AE::cv;
+
+my $api = "$ENV{CLIENT_VER}::Direct";
+my $body = $ENV{ES_BODY} || 'GET';
+my $cxn = $ENV{ES_CXN} || do "default_async_cxn.pl" || die( $@ || $! );
+my $cxn_pool = $ENV{ES_CXN_POOL} || 'Async::Static';
+my @plugins = split /,/, ( $ENV{ES_PLUGINS} || '' );
+our %Auth;
+
+if ( $cxn eq 'Mojo' && !eval { require Mojo::UserAgent; 1 } ) {
+ plan skip_all => 'Mojo::UserAgent not installed';
+ exit;
+}
+
+{
+ no warnings 'redefine';
+
+#===================================
+ sub wait_for {
+#===================================
+ my $promise = shift;
+ my $cv = AE::cv;
+ $promise->done( $cv, sub { $cv->croak(@_) } );
+ $cv->recv;
+ }
+}
+
+my $es;
+if ( $ENV{ES} ) {
+ eval {
+ $es = Search::Elasticsearch::Async->new(
+ nodes => $ENV{ES},
+ trace_to => $trace,
+ cxn => $cxn,
+ cxn_pool => $cxn_pool,
+ client => $api,
+ send_get_body_as => $body,
+ plugins => \@plugins,
+ %Auth
+ );
+ if ( $ENV{ES_SKIP_PING} ) {
+ $cv->send(1);
+ }
+ else {
+ $es->ping->then( sub { $cv->send(@_) }, sub { $cv->croak(@_) } );
+ }
+ $cv->recv;
+ 1;
+ } or do {
+ diag $@;
+ undef $es;
+ };
+}
+
+unless ($es) {
+ plan skip_all => 'No Elasticsearch test node available';
+ exit;
+}
+
+unless ( $ENV{ES_SKIP_PING} ) {
+ my $version = wait_for( $es->info )->{version}{number};
+ my $api = $es->api_version;
+ unless ( $version eq '8.0.0-SNAPSHOT' || ( $api eq '0_90' && $version =~ /^0\.9/
+ || substr( $api, 0, 1 ) eq substr( $version, 0, 1 ) ) )
+ {
+ plan skip_all =>
+ "Tests are for API version $api but Elasticsearch is version $version\n";
+ exit;
+ }
+}
+
+return $es;
+
+unless ( $ENV{ES_SKIP_PING} ) {
+ my $version = wait_for( $es->info )->{version}{number};
+ my $api = $es->api_version;
+ diag "$version - $api\n";
+ die "Tests are for API version $api but Elasticsearch is version $version\n"
+ unless $api eq '0.90' && $version =~ /^0\.9/
+ || substr( $api, 0, 1 ) eq substr( $version, 0, 1 );
+}
+
+return $es;
diff --git a/t/lib/es_async_auth.pl b/t/lib/es_async_auth.pl
new file mode 100644
index 0000000..579d81b
--- /dev/null
+++ b/t/lib/es_async_auth.pl
@@ -0,0 +1,103 @@
+# 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.
+
+#! perl
+use Test::More;
+use Test::Deep;
+use Test::Exception;
+use AE;
+
+use strict;
+use warnings;
+use lib 't/lib';
+
+unless ( $ENV{ES_SSL} ) {
+ plan skip_all => "$ENV{ES_CXN} - No https server specified in ES_SSL";
+ exit;
+}
+
+unless ( $ENV{ES_USERINFO} ) {
+ plan skip_all => "$ENV{ES_CXN} - No user/pass specified in ES_USERINFO";
+ exit;
+}
+
+unless ( $ENV{ES_CA_PATH} ) {
+ plan skip_all => "$ENV{ES_CXN} - No cacert specified in ES_CA_PATH";
+ exit;
+}
+
+$ENV{ES} = $ENV{ES_SSL};
+$ENV{ES_SKIP_PING} = 1;
+
+our %Auth = ( use_https => 1, userinfo => $ENV{ES_USERINFO} );
+
+# Test https connection with correct auth, without cacert
+$ENV{ES_CXN_POOL} = 'Async::Static';
+my $es = do "es_async.pl" or die( $@ || $! );
+ok wait_for( $es->cluster->health ),
+ "$ENV{ES_CXN} - Non-cert HTTPS with auth, cxn static";
+
+$ENV{ES_CXN_POOL} = 'Async::Sniff';
+$es = do "es_async.pl" or die( $@ || $! );
+ok wait_for( $es->cluster->health ),
+ "$ENV{ES_CXN} - Non-cert HTTPS with auth, cxn sniff";
+
+$ENV{ES_CXN_POOL} = 'Async::Static::NoPing';
+$es = do "es_async.pl" or die( $@ || $! );
+ok wait_for( $es->cluster->health ),
+ "$ENV{ES_CXN} - Non-cert HTTPS with auth, cxn noping";
+
+# Test forbidden action
+throws_ok { wait_for( $es->nodes->shutdown ) }
+"Search::Elasticsearch::Error::Forbidden",
+ "$ENV{ES_CXN} - Forbidden action";
+
+# Test https connection with correct auth, with valid cacert
+$Auth{ssl_options} = ssl_options( $ENV{ES_CA_PATH} );
+
+$es = do "es_async.pl" or die( $@ || $! );
+
+ok wait_for( $es->cluster->health ),
+ "$ENV{ES_CXN} - Valid cert HTTPS with auth";
+
+# Test invalid user credentials
+%Auth = ( userinfo => 'foobar:baz' );
+$es = do "es_async.pl" or die( $@ || $! );
+throws_ok { wait_for( $es->cluster->health ) }
+"Search::Elasticsearch::Error::Unauthorized", "$ENV{ES_CXN} - Bad userinfo";
+
+# Test https connection with correct auth, with invalid cacert
+$Auth{ssl_options} = ssl_options('t/lib/bad_cacert.pem');
+
+$es = do "es_async.pl" or die( $@ || $! );
+$ENV{ES} = "https://www.google.com";
+
+throws_ok { wait_for( $es->cluster->health ) }
+"Search::Elasticsearch::Error::SSL",
+ "$ENV{ES_CXN} - Invalid cert throws SSL";
+
+done_testing;
+
+#===================================
+sub wait_for {
+#===================================
+ my $promise = shift;
+ my $cv = AE::cv;
+ $promise->done( $cv, sub { $cv->croak(@_) } );
+ $cv->recv;
+}
+
diff --git a/t/lib/es_async_fork.pl b/t/lib/es_async_fork.pl
new file mode 100644
index 0000000..6c42b18
--- /dev/null
+++ b/t/lib/es_async_fork.pl
@@ -0,0 +1,62 @@
+# 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.
+
+use Test::More;
+use POSIX ":sys_wait_h";
+use AE;
+use Promises qw(deferred);
+
+my $es = do "es_async.pl" or die( $@ || $! );
+my $cxn_class = ref $es->transport->cxn_pool->cxns->[0];
+ok wait_for( $es->info ), "$cxn_class - Info before fork";
+
+my $Kids = 4;
+my %pids;
+
+for my $child ( 1 .. $Kids ) {
+ my $pid = fork();
+ if ($pid) {
+ $pids{$pid} = $child;
+ next;
+ }
+ if ( !defined $pid ) {
+ skip "fork() not supported";
+ done_testing;
+ last;
+ }
+
+ for ( 1 .. 100 ) {
+ wait_for( $es->info );
+ }
+ exit;
+}
+
+my $ok = 0;
+for ( 1 .. 10 ) {
+ my $pid = waitpid( -1, WNOHANG );
+ if ( $pid > 0 ) {
+ delete $pids{$pid};
+ $ok++ unless $?;
+ redo;
+ }
+ last unless keys %pids;
+ sleep 1;
+}
+
+is $ok, $Kids, "$cxn_class - Fork";
+done_testing;
+
diff --git a/t/lib/es_sync.pl b/t/lib/es_sync.pl
index f397867..bb7d3ed 100644
--- a/t/lib/es_sync.pl
+++ b/t/lib/es_sync.pl
@@ -1,3 +1,20 @@
+# 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.
+
use Search::Elasticsearch;
use Test::More;
use strict;
@@ -50,8 +67,8 @@ if ( $ENV{ES} ) {
unless ( $ENV{ES_SKIP_PING} ) {
my $version = $es->info->{version}{number};
my $api = $es->api_version;
- unless ( $api eq '0_90' && $version =~ /^0\.9/
- || substr( $api, 0, 1 ) eq substr( $version, 0, 1 ) )
+ unless ( $version eq '8.0.0-SNAPSHOT' || ( $api eq '0_90' && $version =~ /^0\.9/
+ || substr( $api, 0, 1 ) eq substr( $version, 0, 1 ) ) )
{
plan skip_all =>
"Tests are for API version $api but Elasticsearch is version $version\n";
diff --git a/t/lib/es_sync_auth.pl b/t/lib/es_sync_auth.pl
index 928e098..14469af 100644
--- a/t/lib/es_sync_auth.pl
+++ b/t/lib/es_sync_auth.pl
@@ -1,3 +1,20 @@
+# 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.
+
#! perl
use Test::More;
use Test::Deep;
diff --git a/t/lib/es_sync_fork.pl b/t/lib/es_sync_fork.pl
index 9e1a32c..5339980 100644
--- a/t/lib/es_sync_fork.pl
+++ b/t/lib/es_sync_fork.pl
@@ -1,3 +1,20 @@
+# 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.
+
use Test::More;
use POSIX ":sys_wait_h";
diff --git a/t/lib/index_test_data.pl b/t/lib/index_test_data.pl
index daaf826..6de0baf 100644
--- a/t/lib/index_test_data.pl
+++ b/t/lib/index_test_data.pl
@@ -1,3 +1,20 @@
+# 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.
+
use strict;
use warnings;
use lib 't/lib';
diff --git a/t/lib/index_test_data_7.pl b/t/lib/index_test_data_7.pl
new file mode 100644
index 0000000..591ea9f
--- /dev/null
+++ b/t/lib/index_test_data_7.pl
@@ -0,0 +1,105 @@
+# 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.
+
+use strict;
+use warnings;
+use lib 't/lib';
+
+local $ENV{ES_CXN};
+local $ENV{ES_CXN_POOL};
+my $es = do 'es_sync.pl' or die( $@ || $! );
+
+$es->indices->delete( index => 'test', ignore => 404 );
+$es->indices->create( index => 'test' );
+$es->cluster->health( wait_for_status => 'yellow' );
+
+my $b = $es->bulk_helper(
+ index => 'test'
+);
+my $i = 1;
+for ( names() ) {
+ $b->index(
+ { id => $i,
+ source => {
+ name => $_,
+ count => $i,
+ color => ( $i % 2 ? 'red' : 'green' ),
+ switch => ( $i % 2 ? 1 : 2 )
+ }
+ }
+ );
+ $i++;
+}
+$b->flush;
+$es->indices->refresh;
+
+#===================================
+sub names {
+#===================================
+ return (
+ 'Adaptoid', 'Alpha Ray',
+ 'Alysande Stuart', 'Americop',
+ 'Andrew Chord', 'Android Man',
+ 'Ani-Mator', 'Aqueduct',
+ 'Archangel', 'Arena',
+ 'Auric', 'Barton, Clint',
+ 'Behemoth', 'Bereet',
+ 'Black Death', 'Black King',
+ 'Blaze', 'Cancer',
+ 'Charlie-27', 'Christians, Isaac',
+ 'Clea', 'Contemplator',
+ 'Copperhead', 'Darkdevil',
+ 'Deathbird', 'Diablo',
+ 'Doctor Arthur Nagan', 'Doctor Droom',
+ 'Doctor Octopus', 'Epoch',
+ 'Eternity', 'Feline',
+ 'Firestar', 'Flex',
+ 'Garokk the Petrified Man', 'Gill, Donald "Donny"',
+ 'Glitch', 'Golden Girl',
+ 'Grandmaster', 'Grey, Elaine',
+ 'Halloween Jack', 'Hannibal King',
+ 'Hero for Hire', 'Hrimhari',
+ 'Ikonn', 'Infinity',
+ 'Jack-in-the-Box', 'Jim Hammond',
+ 'Joe Cartelli', 'Juarez, Bonita',
+ 'Judd, Eugene', 'Korrek',
+ 'Krang', 'Kukulcan',
+ 'Lizard', 'Machinesmith',
+ 'Master Man', 'Match',
+ 'Maur-Konn', 'Mekano',
+ 'Miguel Espinosa', 'Mister Sinister',
+ 'Mogul of the Mystic Mountain', 'Mutant Master',
+ 'Night Thrasher', 'Nital, Taj',
+ 'Obituary', 'Ogre',
+ 'Owl', 'Ozone',
+ 'Paris', 'Phastos',
+ 'Piper', 'Prodigy',
+ 'Quagmire', 'Quasar',
+ 'Radioactive Man', 'Rankin, Calvin',
+ 'Scarlet Scarab', 'Scarlet Witch',
+ 'Seth', 'Slug',
+ 'Sluggo', 'Smallwood, Marrina',
+ 'Smith, Tabitha', 'St. Croix, Claudette',
+ 'Stacy X', 'Stallior',
+ 'Star-Dancer', 'Stitch',
+ 'Storm, Susan', 'Summers, Gabriel',
+ 'Thane Ector', 'Toad-In-Waiting',
+ 'Ultron', 'Urich, Phil',
+ 'Vibro', 'Victorius',
+ 'Wolfsbane', 'Yandroth'
+ );
+}
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/share/doc/libsearch-elasticsearch-client-2-0-perl/changelog.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Bulk.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Direct.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Direct::Cat.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Direct::Cluster.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Direct::Indices.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Direct::Nodes.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Direct::Snapshot.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Direct::Tasks.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Role::API.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Role::Bulk.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Role::Scroll.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::Scroll.3pm.gz -rw-r--r-- root/root /usr/share/man/man3/Search::Elasticsearch::Client::2_0::TestServer.3pm.gz -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Bulk.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Direct.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Direct/Cat.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Direct/Cluster.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Direct/Indices.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Direct/Nodes.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Direct/Snapshot.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Direct/Tasks.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Role/API.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Role/Bulk.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Role/Scroll.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/Scroll.pm -rw-r--r-- root/root /usr/share/perl5/Search/Elasticsearch/Client/2_0/TestServer.pm
Control files: lines which differ (wdiff format)
Depends: perl:any, libdevel-globaldestruction-perl, libmoo-perl, libnamespace-clean-perl, libsearch-elasticsearch-perl (>= 6.00), libtry-tiny-perl