diff --git a/prometheus_es_exporter/nodes_stats_parser.py b/prometheus_es_exporter/nodes_stats_parser.py index cb4d0bd..bc941bf 100644 --- a/prometheus_es_exporter/nodes_stats_parser.py +++ b/prometheus_es_exporter/nodes_stats_parser.py @@ -50,8 +50,17 @@ def parse_block(block, metric=None, labels=None): elif isinstance(value, list) and key in bucket_list_keys: bucket_name_key = bucket_list_keys[key] - for n_value in value: - bucket_name = n_value[bucket_name_key] + for n, n_value in enumerate(value): + if bucket_name_key in n_value: + bucket_name = n_value[bucket_name_key] + else: + # If the expected bucket name key isn't present, fall back to using the + # bucket's position in the list as the bucket name. It's not guaranteed that + # the buckets will remain in the same order between calls, but it's the best + # option available. + # e.g. For AWS managed Elasticsearch instances, the `path` key is missing + # from the filesystem `data` directory buckets. + bucket_name = str(n) metrics.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts_ordered(labels, {bucket_name_key: [bucket_name]}))) return metrics diff --git a/tests/test_nodes_stats_parser.py b/tests/test_nodes_stats_parser.py index 3432b7c..8ecc8eb 100644 --- a/tests/test_nodes_stats_parser.py +++ b/tests/test_nodes_stats_parser.py @@ -743,6 +743,39 @@ def test_endpoint(self): result = convert_result(parse_response(response)) self.assertEqual(expected, result) + def test_endpoint_aws(self): + # AWS managed Elasticsearch clusters return modified responses to the /_nodes/stats endpoint. + # (Response trimmed to data with meaningful differences to usual response) + response = { + 'nodes': { + 'bRcKq5zUTAuwNf4qvnXzIQ': { + 'name': 'bRcKq5z', + 'fs': { + 'data': [ + { + # `path` and `mount` keys missing. + # 'path': '/usr/share/elasticsearch/data/nodes/0', + # 'mount': '/usr/share/elasticsearch/data (/dev/mapper/ubuntu--vg-root)', + 'type': 'ext4', + 'total_in_bytes': 233134567424, + 'free_in_bytes': 92206276608, + 'available_in_bytes': 80292356096, + 'spins': 'true' + } + ], + }, + } + } + } + + expected = { + 'fs_data_total_in_bytes{node_id="bRcKq5zUTAuwNf4qvnXzIQ",node_name="bRcKq5z",path="0"}': 233134567424, + 'fs_data_free_in_bytes{node_id="bRcKq5zUTAuwNf4qvnXzIQ",node_name="bRcKq5z",path="0"}': 92206276608, + 'fs_data_available_in_bytes{node_id="bRcKq5zUTAuwNf4qvnXzIQ",node_name="bRcKq5z",path="0"}': 80292356096, + } + result = convert_result(parse_response(response)) + self.assertEqual(expected, result) + if __name__ == '__main__': unittest.main()