Repository: elastic/elasticsearch-py (Python)

Issue:
cluster.state(index='name')['routing_table'] not found in client 7.5.1
Using the most recent release of the Elasticsearch Python client (7.5.1 at this time), the cluster state endpoint is not defaulting to show `_all` keys.

### 7.5.1

```
client.cluster.state(index=idx).keys()
dict_keys(['cluster_name', 'cluster_uuid'])
```

### 7.1.1:

```
client.cluster.state(index=idx).keys()
dict_keys(['cluster_name', 'cluster_uuid', 'version', 'state_uuid', 'master_node', 'blocks', 'nodes', 'metadata', 'routing_table', 'routing_nodes'])
```

This omission, if intended, would seem to be something to change in a major release version, rather than a minor release.

This was detected and reported [here](https://github.com/elastic/support-dev-help/issues/10455#issue-558049925). This compels me to pin Curator at client version `elasticsearch==7.1.0` for now.


Discussion:
Yeah this looks like a regression to me, previously there was logic that made `metric` into `"_all"` if an `index` was given by default but since the API is generated in ffe00d0c3899d200e640e33261d0a324e5dbb5ac it looks like that little piece of logic was lost.

Will have to investigate if any other little tidbits like that were lost as well.



Patch:
diff --git a/elasticsearch/client/cluster.py b/elasticsearch/client/cluster.py
index 0b3dee937..14fa7e943 100644
--- a/elasticsearch/client/cluster.py
+++ b/elasticsearch/client/cluster.py
@@ -102,6 +102,9 @@ def state(self, metric=None, index=None, params=None):
         :arg wait_for_timeout: The maximum time to wait for
             wait_for_metadata_version before timing out
         """
+        if index and metric in SKIP_IN_PATH:
+            metric = "_all"
+
         return self.transport.perform_request(
             "GET", _make_path("_cluster", "state", metric, index), params=params
         )
diff --git a/utils/templates/overrides/cluster/state b/utils/templates/overrides/cluster/state
new file mode 100644
index 000000000..27d599cbf
--- /dev/null
+++ b/utils/templates/overrides/cluster/state
@@ -0,0 +1,8 @@
+{% extends "base" %}
+{% block request %}
+        if index and metric in SKIP_IN_PATH:
+            metric = "_all"
+
+        {{ super()|trim }}
+{% endblock %}
+


Test patch:
diff --git a/test_elasticsearch/test_client/test_cluster.py b/test_elasticsearch/test_client/test_cluster.py
new file mode 100644
index 000000000..a0b9d7414
--- /dev/null
+++ b/test_elasticsearch/test_client/test_cluster.py
@@ -0,0 +1,27 @@
+from test_elasticsearch.test_cases import ElasticsearchTestCase
+
+
+class TestCluster(ElasticsearchTestCase):
+    def test_stats_without_node_id(self):
+        self.client.cluster.stats()
+        self.assert_url_called("GET", "/_cluster/stats")
+
+    def test_stats_with_node_id(self):
+        self.client.cluster.stats("node-1")
+        self.assert_url_called("GET", "/_cluster/stats/nodes/node-1")
+
+        self.client.cluster.stats(node_id="node-2")
+        self.assert_url_called("GET", "/_cluster/stats/nodes/node-2")
+
+    def test_state_with_index_without_metric_defaults_to_all(self):
+        self.client.cluster.state()
+        self.assert_url_called("GET", "/_cluster/state")
+
+        self.client.cluster.state(metric="cluster_name")
+        self.assert_url_called("GET", "/_cluster/state/cluster_name")
+
+        self.client.cluster.state(index="index-1")
+        self.assert_url_called("GET", "/_cluster/state/_all/index-1")
+
+        self.client.cluster.state(index="index-1", metric="cluster_name")
+        self.assert_url_called("GET", "/_cluster/state/cluster_name/index-1")
diff --git a/test_elasticsearch/test_client/test_indices.py b/test_elasticsearch/test_client/test_indices.py
index 7d80562a7..7fdfc7349 100644
--- a/test_elasticsearch/test_client/test_indices.py
+++ b/test_elasticsearch/test_client/test_indices.py
@@ -18,3 +18,7 @@ def test_passing_empty_value_for_required_param_raises_exception(self):
         self.assertRaises(ValueError, self.client.indices.exists, index=None)
         self.assertRaises(ValueError, self.client.indices.exists, index=[])
         self.assertRaises(ValueError, self.client.indices.exists, index="")
+
+    def test_put_mapping_without_index(self):
+        self.client.indices.put_mapping(doc_type="doc-type", body={})
+        self.assert_url_called("PUT", "/_all/doc-type/_mapping")
diff --git a/test_elasticsearch/test_server/__init__.py b/test_elasticsearch/test_server/__init__.py
index 9c1166755..159750a6b 100644
--- a/test_elasticsearch/test_server/__init__.py
+++ b/test_elasticsearch/test_server/__init__.py
@@ -1,3 +1,4 @@
+from unittest import SkipTest
 from elasticsearch.helpers import test
 from elasticsearch.helpers.test import ElasticsearchTestCase as BaseTestCase
 
@@ -6,6 +7,8 @@
 
 def get_client(**kwargs):
     global client
+    if client is False:
+        raise SkipTest("No client is available")
     if client is not None and not kwargs:
         return client
 
@@ -16,7 +19,11 @@ def get_client(**kwargs):
         new_client = local_get_client(**kwargs)
     except ImportError:
         # fallback to using vanilla client
-        new_client = test.get_test_client(**kwargs)
+        try:
+            new_client = test.get_test_client(**kwargs)
+        except SkipTest:
+            client = False
+            raise
 
     if not kwargs:
         client = new_client