Skip to content

Commit a40706f

Browse files
committed
Merge branch 'master' into 8.x
2 parents 15234e4 + d80a7a5 commit a40706f

File tree

17 files changed

+2017
-768
lines changed

17 files changed

+2017
-768
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ localhost.es
33
oneliners.py
44
cacert.pem
55
docs/
6+
docker_test/.env
67
docker_test/repo/
78
docker_test/curatortestenv
89
docker_test/scripts/Dockerfile

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# syntax=docker/dockerfile:1
22
ARG PYVER=3.11.9
3-
ARG ALPTAG=3.19
3+
ARG ALPTAG=3.20
44
FROM python:${PYVER}-alpine${ALPTAG} as builder
55

66
# Add the community repo for access to patchelf binary package

curator/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Curator Version"""
22

3-
__version__ = '8.0.16'
3+
__version__ = '8.0.17'

curator/actions/shrink.py

Lines changed: 192 additions & 106 deletions
Large diffs are not rendered by default.

curator/defaults/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
CLICK_DRYRUN = {
1111
'dry-run': {'help': 'Do not perform any changes.', 'is_flag': True},
1212
}
13+
DATA_NODE_ROLES = ['data', 'data_content', 'data_hot', 'data_warm']
1314

1415
# Click specifics
1516

curator/helpers/date_ops.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ def get_epoch(self, searchme):
3838
3939
:returns: The epoch timestamp extracted from ``searchme`` by regex matching
4040
against :py:attr:`pattern`
41-
:rtype: int
41+
:rtype: int or None
4242
"""
4343
match = self.pattern.search(searchme)
4444
if match:
4545
if match.group("date"):
4646
timestamp = match.group("date")
4747
return datetime_to_epoch(get_datetime(timestamp, self.timestring))
48+
return None
49+
return None
4850

4951

5052
def absolute_date_range(
@@ -161,6 +163,8 @@ def date_range(unit, range_from, range_to, epoch=None, week_starts_on='sunday'):
161163
:rtype: tuple
162164
"""
163165
logger = logging.getLogger(__name__)
166+
start_date = None
167+
start_delta = None
164168
acceptable_units = ['hours', 'days', 'weeks', 'months', 'years']
165169
if unit not in acceptable_units:
166170
raise ConfigurationError(f'"unit" must be one of: {acceptable_units}')

curator/helpers/getters.py

Lines changed: 95 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
"""Utility functions that get things"""
2+
23
import logging
3-
import re
44
from elasticsearch8 import exceptions as es8exc
55
from curator.exceptions import (
6-
ConfigurationError, CuratorException, FailedExecution, MissingArgument)
6+
ConfigurationError,
7+
CuratorException,
8+
FailedExecution,
9+
MissingArgument,
10+
)
11+
712

813
def byte_size(num, suffix='B'):
914
"""
@@ -23,6 +28,23 @@ def byte_size(num, suffix='B'):
2328
num /= 1024.0
2429
return f'{num:.1f}Y{suffix}'
2530

31+
32+
def escape_dots(stringval):
33+
"""
34+
Escape any dots (periods) in ``stringval``.
35+
36+
Primarily used for ``filter_path`` where dots are indicators of path nesting
37+
38+
:param stringval: A string, ostensibly an index name
39+
40+
:type stringval: str
41+
42+
:returns: ``stringval``, but with any periods escaped with a backslash
43+
:retval: str
44+
"""
45+
return stringval.replace('.', r'\.')
46+
47+
2648
def get_alias_actions(oldidx, newidx, aliases):
2749
"""
2850
:param oldidx: The old index name
@@ -34,7 +56,8 @@ def get_alias_actions(oldidx, newidx, aliases):
3456
:type aliases: dict
3557
3658
:returns: A list of actions suitable for
37-
:py:meth:`~.elasticsearch.client.IndicesClient.update_aliases` ``actions`` kwarg.
59+
:py:meth:`~.elasticsearch.client.IndicesClient.update_aliases` ``actions``
60+
kwarg.
3861
:rtype: list
3962
"""
4063
actions = []
@@ -43,29 +66,40 @@ def get_alias_actions(oldidx, newidx, aliases):
4366
actions.append({'add': {'index': newidx, 'alias': alias}})
4467
return actions
4568

69+
4670
def get_data_tiers(client):
4771
"""
48-
Get all valid data tiers from the node roles of each node in the cluster by polling each node
72+
Get all valid data tiers from the node roles of each node in the cluster by
73+
polling each node
4974
5075
:param client: A client connection object
5176
:type client: :py:class:`~.elasticsearch.Elasticsearch`
5277
5378
:returns: The available data tiers in ``tier: bool`` form.
5479
:rtype: dict
5580
"""
81+
5682
def role_check(role, node_info):
5783
if role in node_info['roles']:
5884
return True
5985
return False
86+
6087
info = client.nodes.info()['nodes']
61-
retval = {'data_hot': False, 'data_warm': False, 'data_cold': False, 'data_frozen': False}
88+
retval = {
89+
'data_hot': False,
90+
'data_warm': False,
91+
'data_cold': False,
92+
'data_frozen': False,
93+
}
6294
for node in info:
6395
for role in ['data_hot', 'data_warm', 'data_cold', 'data_frozen']:
64-
# This guarantees we don't overwrite a True with a False. We only add True values
96+
# This guarantees we don't overwrite a True with a False.
97+
# We only add True values
6598
if role_check(role, info[node]):
6699
retval[role] = True
67100
return retval
68101

102+
69103
def get_indices(client, search_pattern='_all'):
70104
"""
71105
Calls :py:meth:`~.elasticsearch.client.CatClient.indices`
@@ -79,10 +113,14 @@ def get_indices(client, search_pattern='_all'):
79113
logger = logging.getLogger(__name__)
80114
indices = []
81115
try:
82-
# Doing this in two stages because IndexList also calls for these args, and the unit tests
83-
# need to Mock this call the same exact way.
116+
# Doing this in two stages because IndexList also calls for these args,
117+
# and the unit tests need to Mock this call the same exact way.
84118
resp = client.cat.indices(
85-
index=search_pattern, expand_wildcards='open,closed', h='index,status', format='json')
119+
index=search_pattern,
120+
expand_wildcards='open,closed',
121+
h='index,status',
122+
format='json',
123+
)
86124
except Exception as err:
87125
raise FailedExecution(f'Failed to get indices. Error: {err}') from err
88126
if not resp:
@@ -92,6 +130,7 @@ def get_indices(client, search_pattern='_all'):
92130
logger.debug('All indices: %s', indices)
93131
return indices
94132

133+
95134
def get_repository(client, repository=''):
96135
"""
97136
Calls :py:meth:`~.elasticsearch.client.SnapshotClient.get_repository`
@@ -114,6 +153,7 @@ def get_repository(client, repository=''):
114153
)
115154
raise CuratorException(msg) from err
116155

156+
117157
def get_snapshot(client, repository=None, snapshot=''):
118158
"""
119159
Calls :py:meth:`~.elasticsearch.client.SnapshotClient.get`
@@ -126,9 +166,10 @@ def get_snapshot(client, repository=None, snapshot=''):
126166
:type repository: str
127167
:type snapshot: str
128168
129-
:returns: Information about the provided ``snapshot``, a snapshot (or a comma-separated list of
130-
snapshots). If no snapshot specified, it will collect info for all snapshots. If none
131-
exist, an empty :py:class:`dict` will be returned.
169+
:returns: Information about the provided ``snapshot``, a snapshot (or a
170+
comma-separated list of snapshots). If no snapshot specified, it will
171+
collect info for all snapshots. If none exist, an empty :py:class:`dict`
172+
will be returned.
132173
:rtype: dict
133174
"""
134175
if not repository:
@@ -143,6 +184,7 @@ def get_snapshot(client, repository=None, snapshot=''):
143184
)
144185
raise FailedExecution(msg) from err
145186

187+
146188
def get_snapshot_data(client, repository=None):
147189
"""
148190
Get all snapshots from repository and return a list.
@@ -168,6 +210,7 @@ def get_snapshot_data(client, repository=None):
168210
)
169211
raise FailedExecution(msg) from err
170212

213+
171214
def get_tier_preference(client, target_tier='data_frozen'):
172215
"""Do the tier preference thing in reverse order from coldest to hottest
173216
Based on the value of ``target_tier``, build out the list to use.
@@ -194,8 +237,8 @@ def get_tier_preference(client, target_tier='data_frozen'):
194237
if tier in tiers and tiermap[tier] <= tiermap[target_tier]:
195238
test_list.insert(0, tier)
196239
if target_tier == 'data_frozen':
197-
# We're migrating to frozen here. If a frozen tier exists, frozen searchable snapshot
198-
# mounts should only ever go to the frozen tier.
240+
# We're migrating to frozen here. If a frozen tier exists, frozen searchable
241+
# snapshot mounts should only ever go to the frozen tier.
199242
if 'data_frozen' in tiers and tiers['data_frozen']:
200243
return 'data_frozen'
201244
# If there are no nodes with the 'data_frozen' role...
@@ -207,9 +250,11 @@ def get_tier_preference(client, target_tier='data_frozen'):
207250
# If all of these are false, then we have no data tiers and must use 'data_content'
208251
if not preflist:
209252
return 'data_content'
210-
# This will join from coldest to hottest as csv string, e.g. 'data_cold,data_warm,data_hot'
253+
# This will join from coldest to hottest as csv string,
254+
# e.g. 'data_cold,data_warm,data_hot'
211255
return ','.join(preflist)
212256

257+
213258
def get_write_index(client, alias):
214259
"""
215260
Calls :py:meth:`~.elasticsearch.client.IndicesClient.get_alias`
@@ -220,7 +265,8 @@ def get_write_index(client, alias):
220265
:type client: :py:class:`~.elasticsearch.Elasticsearch`
221266
:type alias: str
222267
223-
:returns: The the index name associated with the alias that is designated ``is_write_index``
268+
:returns: The the index name associated with the alias that is designated
269+
``is_write_index``
224270
:rtype: str
225271
"""
226272
try:
@@ -229,17 +275,21 @@ def get_write_index(client, alias):
229275
raise CuratorException(f'Alias {alias} not found') from exc
230276
# If there are more than one in the list, one needs to be the write index
231277
# otherwise the alias is a one to many, and can't do rollover.
278+
retval = None
232279
if len(list(response.keys())) > 1:
233280
for index in list(response.keys()):
234281
try:
235282
if response[index]['aliases'][alias]['is_write_index']:
236-
return index
283+
retval = index
237284
except KeyError as exc:
238285
raise FailedExecution(
239-
'Invalid alias: is_write_index not found in 1 to many alias') from exc
286+
'Invalid alias: is_write_index not found in 1 to many alias'
287+
) from exc
240288
else:
241289
# There's only one, so this is it
242-
return list(response.keys())[0]
290+
retval = list(response.keys())[0]
291+
return retval
292+
243293

244294
def index_size(client, idx, value='total'):
245295
"""
@@ -256,7 +306,11 @@ def index_size(client, idx, value='total'):
256306
:returns: The sum of either ``primaries`` or ``total`` shards for index ``idx``
257307
:rtype: integer
258308
"""
259-
return client.indices.stats(index=idx)['indices'][idx][value]['store']['size_in_bytes']
309+
fpath = f'indices.{escape_dots(idx)}.{value}.store.size_in_bytes'
310+
return client.indices.stats(index=idx, filter_path=fpath)['indices'][idx][value][
311+
'store'
312+
]['size_in_bytes']
313+
260314

261315
def meta_getter(client, idx, get=None):
262316
"""Meta Getter
@@ -297,9 +351,10 @@ def meta_getter(client, idx, get=None):
297351
logger.error('Exception encountered: %s', exc)
298352
return retval
299353

354+
300355
def name_to_node_id(client, name):
301356
"""
302-
Calls :py:meth:`~.elasticsearch.client.NodesClient.stats`
357+
Calls :py:meth:`~.elasticsearch.client.NodesClient.info`
303358
304359
:param client: A client connection object
305360
:param name: The node ``name``
@@ -311,17 +366,19 @@ def name_to_node_id(client, name):
311366
:rtype: str
312367
"""
313368
logger = logging.getLogger(__name__)
314-
stats = client.nodes.stats()
315-
for node in stats['nodes']:
316-
if stats['nodes'][node]['name'] == name:
369+
fpath = 'nodes'
370+
info = client.nodes.info(filter_path=fpath)
371+
for node in info['nodes']:
372+
if info['nodes'][node]['name'] == name:
317373
logger.debug('Found node_id "%s" for name "%s".', node, name)
318374
return node
319375
logger.error('No node_id found matching name: "%s"', name)
320376
return None
321377

378+
322379
def node_id_to_name(client, node_id):
323380
"""
324-
Calls :py:meth:`~.elasticsearch.client.NodesClient.stats`
381+
Calls :py:meth:`~.elasticsearch.client.NodesClient.info`
325382
326383
:param client: A client connection object
327384
:param node_id: The node ``node_id``
@@ -333,15 +390,17 @@ def node_id_to_name(client, node_id):
333390
:rtype: str
334391
"""
335392
logger = logging.getLogger(__name__)
336-
stats = client.nodes.stats()
393+
fpath = f'nodes.{node_id}.name'
394+
info = client.nodes.info(filter_path=fpath)
337395
name = None
338-
if node_id in stats['nodes']:
339-
name = stats['nodes'][node_id]['name']
396+
if node_id in info['nodes']:
397+
name = info['nodes'][node_id]['name']
340398
else:
341399
logger.error('No node_id found matching: "%s"', node_id)
342400
logger.debug('Name associated with node_id "%s": %s', node_id, name)
343401
return name
344402

403+
345404
def node_roles(client, node_id):
346405
"""
347406
Calls :py:meth:`~.elasticsearch.client.NodesClient.info`
@@ -355,12 +414,14 @@ def node_roles(client, node_id):
355414
:returns: The list of roles assigned to the node identified by ``node_id``
356415
:rtype: list
357416
"""
358-
return client.nodes.info()['nodes'][node_id]['roles']
417+
fpath = f'nodes.{node_id}.roles'
418+
return client.nodes.info(filter_path=fpath)['nodes'][node_id]['roles']
419+
359420

360421
def single_data_path(client, node_id):
361422
"""
362-
In order for a shrink to work, it should be on a single filesystem, as shards cannot span
363-
filesystems. Calls :py:meth:`~.elasticsearch.client.NodesClient.stats`
423+
In order for a shrink to work, it should be on a single filesystem, as shards
424+
cannot span filesystems. Calls :py:meth:`~.elasticsearch.client.NodesClient.stats`
364425
365426
:param client: A client connection object
366427
:param node_id: The node ``node_id``
@@ -371,4 +432,6 @@ def single_data_path(client, node_id):
371432
:returns: ``True`` if the node has a single filesystem, else ``False``
372433
:rtype: bool
373434
"""
374-
return len(client.nodes.stats()['nodes'][node_id]['fs']['data']) == 1
435+
fpath = f'nodes.{node_id}.fs.data'
436+
response = client.nodes.stats(filter_path=fpath)
437+
return len(response['nodes'][node_id]['fs']['data']) == 1

0 commit comments

Comments
 (0)