Coverage for /Users/buh/.pyenv/versions/3.12.9/envs/es-testbed/lib/python3.12/site-packages/es_testbed/helpers/es_api.py: 99%
400 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-31 13:12 -0600
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-31 13:12 -0600
1"""Functions that make Elasticsearch API Calls"""
3# pylint: disable=R0913,R0917,W0707
4import typing as t
5import logging
6from os import getenv
7import tiered_debug as debug
8from elasticsearch8.exceptions import NotFoundError, TransportError
9from es_wait import Exists, Snapshot
10from es_wait.exceptions import EsWaitFatal, EsWaitTimeout
11from ..defaults import MAPPING, PAUSE_DEFAULT, PAUSE_ENVVAR
12from ..exceptions import (
13 NameChanged,
14 ResultNotExpected,
15 TestbedFailure,
16 TestbedMisconfig,
17)
18from ..helpers.utils import (
19 get_routing,
20 mounted_name,
21 prettystr,
22 storage_type,
23)
25if t.TYPE_CHECKING:
26 from elasticsearch8 import Elasticsearch
28PAUSE_VALUE = float(getenv(PAUSE_ENVVAR, default=PAUSE_DEFAULT))
30logger = logging.getLogger(__name__)
33def emap(kind: str, es: 'Elasticsearch', value=None) -> t.Dict[str, t.Any]:
34 """Return a value from a dictionary"""
35 _ = {
36 'alias': {
37 'delete': es.indices.delete_alias,
38 'exists': es.indices.exists_alias,
39 'get': es.indices.get_alias,
40 'kwargs': {'index': value, 'expand_wildcards': ['open', 'closed']},
41 'plural': 'alias(es)',
42 },
43 'data_stream': {
44 'delete': es.indices.delete_data_stream,
45 'exists': es.indices.exists,
46 'get': es.indices.get_data_stream,
47 'kwargs': {'name': value, 'expand_wildcards': ['open', 'closed']},
48 'plural': 'data_stream(s)',
49 'key': 'data_streams',
50 },
51 'index': {
52 'delete': es.indices.delete,
53 'exists': es.indices.exists,
54 'get': es.indices.get,
55 'kwargs': {'index': value, 'expand_wildcards': ['open', 'closed']},
56 'plural': 'index(es)',
57 },
58 'template': {
59 'delete': es.indices.delete_index_template,
60 'exists': es.indices.exists_index_template,
61 'get': es.indices.get_index_template,
62 'kwargs': {'name': value},
63 'plural': 'index template(s)',
64 'key': 'index_templates',
65 },
66 'ilm': {
67 'delete': es.ilm.delete_lifecycle,
68 'exists': es.ilm.get_lifecycle,
69 'get': es.ilm.get_lifecycle,
70 'kwargs': {'name': value},
71 'plural': 'ilm policy(ies)',
72 },
73 'component': {
74 'delete': es.cluster.delete_component_template,
75 'exists': es.cluster.exists_component_template,
76 'get': es.cluster.get_component_template,
77 'kwargs': {'name': value},
78 'plural': 'component template(s)',
79 'key': 'component_templates',
80 },
81 'snapshot': {
82 'delete': es.snapshot.delete,
83 'exists': es.snapshot.get,
84 'get': es.snapshot.get,
85 'kwargs': {'snapshot': value},
86 'plural': 'snapshot(s)',
87 },
88 }
89 return _[kind]
92def change_ds(client: 'Elasticsearch', actions: t.Optional[str] = None) -> None:
93 """Change/Modify/Update a data_stream"""
94 debug.lv2('Starting function...')
95 try:
96 debug.lv4('TRY: client.indices.modify_data_stream')
97 debug.lv5(f'modify_data_stream actions: {actions}')
98 res = client.indices.modify_data_stream(actions=actions, body=None)
99 debug.lv5(f'modify_data_stream response: {res}')
100 except Exception as err:
101 debug.lv3('Exiting function, raising exception')
102 debug.lv5(f'Exception: {prettystr(err)}')
103 raise ResultNotExpected(
104 f'Unable to modify data_streams. {prettystr(err)}'
105 ) from err
106 debug.lv3('Exiting function')
109def wait_wrapper(
110 client: 'Elasticsearch',
111 wait_cls: t.Callable,
112 wait_kwargs: t.Dict,
113 func: t.Callable,
114 f_kwargs: t.Dict,
115) -> None:
116 """Wrapper function for waiting on an object to be created"""
117 debug.lv2('Starting function...')
118 try:
119 debug.lv4('TRY: func()')
120 debug.lv5(f'func kwargs: {f_kwargs}')
121 func(**f_kwargs)
122 debug.lv4('TRY: wait_cls')
123 debug.lv5(f'wait_cls kwargs: {wait_kwargs}')
124 test = wait_cls(client, **wait_kwargs)
125 debug.lv4('TRY: wait()')
126 test.wait()
127 except EsWaitFatal as wait:
128 msg = f'{wait.message}. Elapsed time: {wait.elapsed}. Errors: {wait.errors}'
129 debug.lv3('Exiting function, raising exception')
130 debug.lv5(f'Exception: {prettystr(wait)}')
131 raise TestbedFailure(msg) from wait
132 except EsWaitTimeout as wait:
133 msg = f'{wait.message}. Elapsed time: {wait.elapsed}. Timeout: {wait.timeout}'
134 debug.lv3('Exiting function, raising exception')
135 debug.lv5(f'Exception: {prettystr(wait)}')
136 raise TestbedFailure(msg) from wait
137 except TransportError as err:
138 debug.lv3('Exiting function, raising exception')
139 debug.lv5(f'Exception: {prettystr(err)}')
140 raise TestbedFailure(
141 f'Elasticsearch TransportError class exception encountered:'
142 f'{prettystr(err)}'
143 ) from err
144 except Exception as err:
145 debug.lv3('Exiting function, raising exception')
146 debug.lv5(f'Exception: {prettystr(err)}')
147 raise TestbedFailure(f'General Exception caught: {prettystr(err)}') from err
148 debug.lv3('Exiting function')
151def create_data_stream(client: 'Elasticsearch', name: str) -> None:
152 """Create a data_stream"""
153 debug.lv2('Starting function...')
154 wait_kwargs = {'name': name, 'kind': 'data_stream', 'pause': PAUSE_VALUE}
155 debug.lv5(f'wait_kwargs: {wait_kwargs}')
156 f_kwargs = {'name': name}
157 debug.lv5(f'f_kwargs: {f_kwargs}')
158 debug.lv5(f'Creating data_stream {name} and waiting for it to exist')
159 wait_wrapper(
160 client, Exists, wait_kwargs, client.indices.create_data_stream, f_kwargs
161 )
162 debug.lv3('Exiting function')
165def create_index(
166 client: 'Elasticsearch',
167 name: str,
168 aliases: t.Union[t.Dict, None] = None,
169 settings: t.Union[t.Dict, None] = None,
170 tier: str = 'hot',
171) -> None:
172 """Create named index"""
173 debug.lv2('Starting function...')
174 if not settings:
175 settings = get_routing(tier=tier)
176 else:
177 settings.update(get_routing(tier=tier))
178 debug.lv5(f'settings: {settings}')
179 wait_kwargs = {'name': name, 'kind': 'index', 'pause': PAUSE_VALUE}
180 debug.lv5(f'wait_kwargs: {wait_kwargs}')
181 f_kwargs = {
182 'index': name,
183 'aliases': aliases,
184 'mappings': MAPPING,
185 'settings': settings,
186 }
187 debug.lv5(f'f_kwargs: {f_kwargs}')
188 debug.lv5(f'Creating index {name} and waiting for it to exist')
189 wait_wrapper(client, Exists, wait_kwargs, client.indices.create, f_kwargs)
190 retval = exists(client, 'index', name)
191 debug.lv3('Exiting function, returning value')
192 debug.lv5(f'Value = {retval}')
193 return retval
196def verify(
197 client: 'Elasticsearch',
198 kind: str,
199 name: str,
200 repository: t.Optional[str] = None,
201) -> bool:
202 """Verify that whatever was deleted is actually deleted"""
203 debug.lv2('Starting function...')
204 success = True
205 items = name.split(',')
206 for item in items:
207 result = exists(client, kind, item, repository=repository)
208 if result: # That means it's still in the cluster
209 success = False
210 debug.lv3('Exiting function, returning value')
211 debug.lv5(f'Value = {success}')
212 return success
215def delete(
216 client: 'Elasticsearch',
217 kind: str,
218 name: str,
219 repository: t.Optional[str] = None,
220) -> bool:
221 """Delete the named object of type kind"""
222 debug.lv2('Starting function...')
223 which = emap(kind, client)
224 func = which['delete']
225 success = False
226 if name is not None: # Typically only with ilm
227 try:
228 debug.lv4('TRY: func')
229 if kind == 'snapshot':
230 debug.lv5(f'Deleting snapshot {name} from repository {repository}')
231 res = func(snapshot=name, repository=repository)
232 elif kind == 'index':
233 debug.lv5(f'Deleting index {name}')
234 res = func(index=name)
235 else:
236 debug.lv5(f'Deleting {kind} {name}')
237 res = func(name=name)
238 except NotFoundError as err:
239 debug.lv5(f'{kind} named {name} not found: {prettystr(err)}')
240 debug.lv3('Exiting function, returning value')
241 debug.lv5('Value = True')
242 return True
243 except Exception as err:
244 debug.lv3('Exiting function, raising exception')
245 debug.lv5(f'Exception: {prettystr(err)}')
246 raise ResultNotExpected(f'Unexpected result: {prettystr(err)}') from err
247 if 'acknowledged' in res and res['acknowledged']:
248 success = True
249 debug.lv3(f'Deleted {which["plural"]}: "{name}"')
250 else:
251 debug.lv5('Verifying deletion manually')
252 success = verify(client, kind, name, repository=repository)
253 else:
254 debug.lv3(f'"{kind}" has a None value for name')
255 debug.lv3('Exiting function, returning value')
256 debug.lv5(f'Value = {success}')
257 return success
260def do_snap(
261 client: 'Elasticsearch', repo: str, snap: str, idx: str, tier: str = 'cold'
262) -> None:
263 """Perform a snapshot"""
264 debug.lv2('Starting function...')
265 wait_kwargs = {'snapshot': snap, 'repository': repo, 'pause': 1, 'timeout': 60}
266 debug.lv5(f'wait_kwargs: {wait_kwargs}')
267 f_kwargs = {'repository': repo, 'snapshot': snap, 'indices': idx}
268 debug.lv5(f'f_kwargs: {f_kwargs}')
269 debug.lv5(f'Creating snapshot {snap} and waiting for it to complete')
270 wait_wrapper(client, Snapshot, wait_kwargs, client.snapshot.create, f_kwargs)
272 # Mount the index accordingly
273 debug.lv5(
274 f'Mounting index {idx} from snapshot {snap} as searchable snapshot '
275 f'with mounted name: {mounted_name(idx, tier)}'
276 )
277 client.searchable_snapshots.mount(
278 repository=repo,
279 snapshot=snap,
280 index=idx,
281 index_settings=get_routing(tier=tier),
282 renamed_index=mounted_name(idx, tier),
283 storage=storage_type(tier),
284 wait_for_completion=True,
285 )
286 # Fix aliases
287 debug.lv5(f'Fixing aliases for {idx} to point to {mounted_name(idx, tier)}')
288 fix_aliases(client, idx, mounted_name(idx, tier))
289 debug.lv3('Exiting function')
292def exists(
293 client: 'Elasticsearch', kind: str, name: str, repository: t.Union[str, None] = None
294) -> bool:
295 """Return boolean existence of the named kind of object"""
296 debug.lv2('Starting function...')
297 if name is None:
298 return False
299 retval = True
300 func = emap(kind, client)['exists']
301 try:
302 debug.lv4('TRY: func')
303 if kind == 'snapshot':
304 # Expected response: {'snapshots': [{'snapshot': name, ...}]}
305 # Since we are specifying by name, there should only be one returned
306 debug.lv5(f'Checking for snapshot {name} in repository {repository}')
307 res = func(snapshot=name, repository=repository)
308 debug.lv3(f'Snapshot response: {res}')
309 # If there are no entries, load a default None value for the check
310 _ = dict(res['snapshots'][0]) if res else {'snapshot': None}
311 # Since there should be only 1 snapshot with this name, we can check it
312 retval = bool(_['snapshot'] == name)
313 elif kind == 'ilm':
314 # There is no true 'exists' method for ILM, so we have to get the policy
315 # and check for a NotFoundError
316 debug.lv5(f'Checking for ILM policy {name}')
317 retval = bool(name in dict(func(name=name)))
318 elif kind in ['index', 'data_stream']:
319 debug.lv5(f'Checking for {kind} {name}')
320 retval = func(index=name)
321 else:
322 debug.lv5(f'Checking for {kind} {name}')
323 retval = func(name=name)
324 except NotFoundError:
325 debug.lv5(f'{kind} named {name} not found')
326 retval = False
327 except Exception as err:
328 debug.lv3('Exiting function, raising exception')
329 debug.lv5(f'Exception: {prettystr(err)}')
330 raise ResultNotExpected(f'Unexpected result: {prettystr(err)}') from err
331 debug.lv3('Exiting function, returning value')
332 debug.lv5(f'Value = {retval}')
333 return retval
336def fill_index(
337 client: 'Elasticsearch',
338 name: t.Optional[str] = None,
339 doc_generator: t.Optional[t.Generator[t.Dict, None, None]] = None,
340 options: t.Optional[t.Dict] = None,
341) -> None:
342 """
343 Create and fill the named index with mappings and settings as directed
345 :param client: ES client
346 :param name: Index name
347 :param doc_generator: The generator function
349 :returns: No return value
350 """
351 debug.lv2('Starting function...')
352 if not options:
353 options = {}
354 for doc in doc_generator(**options):
355 client.index(index=name, document=doc)
356 client.indices.flush(index=name)
357 client.indices.refresh(index=name)
358 debug.lv3('Exiting function')
361def find_write_index(client: 'Elasticsearch', name: str) -> t.AnyStr:
362 """Find the write_index for an alias by searching any index the alias points to"""
363 debug.lv2('Starting function...')
364 retval = None
365 for alias in get_aliases(client, name):
366 debug.lv5(f'Inspecting alias: {alias}')
367 retval = get_write_index(client, alias)
368 debug.lv5(f'find_write_index response: {retval}')
369 if retval:
370 debug.lv5(f'Found write index: {retval}')
371 break
372 debug.lv3('Exiting function, returning value')
373 debug.lv5(f'Value = {retval}')
374 return retval
377def fix_aliases(client: 'Elasticsearch', oldidx: str, newidx: str) -> None:
378 """Fix aliases using the new and old index names as data"""
379 debug.lv2('Starting function...')
380 # Delete the original index
381 debug.lv5(f'Deleting index {oldidx}')
382 client.indices.delete(index=oldidx)
383 # Add the original index name as an alias to the mounted index
384 debug.lv5(f'Adding alias {oldidx} to index {newidx}')
385 client.indices.put_alias(index=f'{newidx}', name=oldidx)
386 debug.lv3('Exiting function')
389def get(
390 client: 'Elasticsearch',
391 kind: str,
392 pattern: str,
393 repository: t.Optional[str] = None,
394) -> t.Sequence[str]:
395 """get any/all objects of type kind matching pattern"""
396 debug.lv2('Starting function...')
397 if pattern is None:
398 msg = f'"{kind}" has a None value for pattern'
399 logger.error(msg)
400 raise TestbedMisconfig(msg)
401 which = emap(kind, client, value=pattern)
402 func = which['get']
403 kwargs = which['kwargs']
404 if kind == 'snapshot':
405 kwargs['repository'] = repository
406 try:
407 debug.lv4('TRY: func')
408 debug.lv5(f'func kwargs: {kwargs}')
409 result = func(**kwargs)
410 except NotFoundError:
411 debug.lv3(f'{kind} pattern "{pattern}" had zero matches')
412 return []
413 except Exception as err:
414 raise ResultNotExpected(f'Unexpected result: {prettystr(err)}') from err
415 if kind == 'snapshot':
416 debug.lv5('Checking for snapshot')
417 retval = [x['snapshot'] for x in result['snapshots']]
418 elif kind in ['data_stream', 'template', 'component']:
419 debug.lv5('Checking for data_stream/template/component')
420 retval = [x['name'] for x in result[which['key']]]
421 else:
422 debug.lv5('Checking for alias/ilm/index')
423 retval = list(result.keys())
424 debug.lv3('Exiting function, returning value')
425 debug.lv5(f'Value = {retval}')
426 return retval
429def get_aliases(client: 'Elasticsearch', name: str) -> t.Sequence[str]:
430 """Get aliases from index 'name'"""
431 debug.lv2('Starting function...')
432 res = client.indices.get(index=name)
433 debug.lv5(f'get_aliases response: {res}')
434 try:
435 debug.lv4('TRY: getting aliases')
436 retval = list(res[name]['aliases'].keys())
437 debug.lv5(f"list(res[name]['aliases'].keys()) = {retval}")
438 except KeyError:
439 retval = None
440 debug.lv3('Exiting function, returning value')
441 debug.lv5(f'Value = {retval}')
442 return retval
445def get_backing_indices(client: 'Elasticsearch', name: str) -> t.Sequence[str]:
446 """Get the backing indices from the named data_stream"""
447 debug.lv2('Starting function...')
448 resp = resolver(client, name)
449 data_streams = resp['data_streams']
450 retval = []
451 if data_streams:
452 debug.lv5('Checking for backing indices...')
453 if len(data_streams) > 1:
454 debug.lv3('Exiting function, raising exception')
455 debug.lv5(f'ResultNotExpected: More than 1 found {data_streams}')
456 raise ResultNotExpected(
457 f'Expected only a single data_stream matching {name}'
458 )
459 retval = data_streams[0]['backing_indices']
460 debug.lv3('Exiting function, returning value')
461 debug.lv5(f'Value = {retval}')
462 return retval
465def get_ds_current(client: 'Elasticsearch', name: str) -> str:
466 """
467 Find which index is the current 'write' index of the data_stream
468 This is best accomplished by grabbing the last backing_index
469 """
470 debug.lv2('Starting function...')
471 backers = get_backing_indices(client, name)
472 retval = None
473 if backers:
474 retval = backers[-1]
475 debug.lv3('Exiting function, returning value')
476 debug.lv5(f'Value = {retval}')
477 return retval
480def get_ilm(client: 'Elasticsearch', pattern: str) -> t.Union[t.Dict[str, str], None]:
481 """Get any ILM entity in ES that matches pattern"""
482 debug.lv2('Starting function...')
483 try:
484 debug.lv4('TRY: ilm.get_lifecycle')
485 retval = client.ilm.get_lifecycle(name=pattern)
486 except Exception as err:
487 msg = f'Unable to get ILM lifecycle matching {pattern}. Error: {prettystr(err)}'
488 logger.critical(msg)
489 debug.lv3('Exiting function, raising exception')
490 debug.lv5(f'Exception: {prettystr(err)}')
491 raise ResultNotExpected(msg) from err
492 debug.lv3('Exiting function, returning value')
493 debug.lv5(f'Value = {retval}')
494 return retval
497def get_ilm_phases(client: 'Elasticsearch', name: str) -> t.Dict:
498 """Return the policy/phases part of the ILM policy identified by 'name'"""
499 debug.lv2('Starting function...')
500 ilm = get_ilm(client, name)
501 try:
502 debug.lv4('TRY: get ILM phases')
503 retval = ilm[name]['policy']['phases']
504 except KeyError as err:
505 msg = f'Unable to get ILM lifecycle named {name}. Error: {prettystr(err)}'
506 logger.critical(msg)
507 debug.lv3('Exiting function, raising exception')
508 debug.lv5(f'Exception: {prettystr(err)}')
509 raise ResultNotExpected(msg) from err
510 debug.lv3('Exiting function, returning value')
511 debug.lv5(f'Value = {retval}')
512 return retval
515def get_write_index(client: 'Elasticsearch', name: str) -> str:
516 """
517 Calls :py:meth:`~.elasticsearch.client.IndicesClient.get_alias`
519 :param client: A client connection object
520 :param name: An alias name
522 :type client: :py:class:`~.elasticsearch.Elasticsearch`
524 :returns: The the index name associated with the alias that is designated
525 ``is_write_index``
526 """
527 debug.lv2('Starting function...')
528 response = client.indices.get_alias(index=name)
529 debug.lv5(f'get_alias response: {response}')
530 retval = None
531 for index in list(response.keys()):
532 try:
533 debug.lv4('TRY: get write index')
534 if response[index]['aliases'][name]['is_write_index']:
535 retval = index
536 break
537 except KeyError:
538 continue
539 debug.lv3('Exiting function, returning value')
540 debug.lv5(f'Value = {retval}')
541 return retval
544def ilm_explain(client: 'Elasticsearch', name: str) -> t.Union[t.Dict, None]:
545 """Return the results from the ILM Explain API call for the named index"""
546 debug.lv2('Starting function...')
547 try:
548 debug.lv4('TRY: ilm.explain_lifecycle')
549 retval = client.ilm.explain_lifecycle(index=name)['indices'][name]
550 except KeyError:
551 debug.lv5('Index name changed')
552 new = list(client.ilm.explain_lifecycle(index=name)['indices'].keys())[0]
553 debug.lv5(f'ilm.explain_lifecycle response: {new}')
554 retval = client.ilm.explain_lifecycle(index=new)['indices'][new]
555 except NotFoundError as err:
556 logger.warning(f'Datastream/Index Name changed. {name} was not found')
557 debug.lv3('Exiting function, raising exception')
558 debug.lv5(f'Exception: {prettystr(err)}')
559 raise NameChanged(f'{name} was not found, likely due to a name change') from err
560 except Exception as err:
561 msg = f'Unable to get ILM information for index {name}'
562 logger.critical(msg)
563 debug.lv3('Exiting function, raising exception')
564 debug.lv5(f'Exception: {prettystr(err)}')
565 raise ResultNotExpected(f'{msg}. Exception: {prettystr(err)}') from err
566 debug.lv3('Exiting function, returning value')
567 debug.lv5(f'Value = {retval}')
568 return retval
571def ilm_move(
572 client: 'Elasticsearch', name: str, current_step: t.Dict, next_step: t.Dict
573) -> None:
574 """Move index 'name' from the current step to the next step"""
575 debug.lv2('Starting function...')
576 try:
577 debug.lv4('TRY: ilm.move_to_step')
578 res = client.ilm.move_to_step(
579 index=name, current_step=current_step, next_step=next_step
580 )
581 debug.lv5(f'ilm.move_to_step response: {res}')
582 except Exception as err:
583 msg = (
584 f'Unable to move index {name} to ILM next step: {next_step}. '
585 f'Error: {prettystr(err)}'
586 )
587 logger.critical(msg)
588 raise ResultNotExpected(msg, (err,))
589 debug.lv3('Exiting function')
592def put_comp_tmpl(client: 'Elasticsearch', name: str, component: t.Dict) -> None:
593 """Publish a component template"""
594 debug.lv2('Starting function...')
595 wait_kwargs = {'name': name, 'kind': 'component_template', 'pause': PAUSE_VALUE}
596 f_kwargs = {'name': name, 'template': component, 'create': True}
597 wait_wrapper(
598 client,
599 Exists,
600 wait_kwargs,
601 client.cluster.put_component_template,
602 f_kwargs,
603 )
604 debug.lv3('Exiting function')
607def put_idx_tmpl(
608 client: 'Elasticsearch',
609 name: str,
610 index_patterns: t.List[str],
611 components: t.List[str],
612 data_stream: t.Optional[t.Dict] = None,
613) -> None:
614 """Publish an index template"""
615 debug.lv2('Starting function...')
616 wait_kwargs = {'name': name, 'kind': 'index_template', 'pause': PAUSE_VALUE}
617 f_kwargs = {
618 'name': name,
619 'composed_of': components,
620 'data_stream': data_stream,
621 'index_patterns': index_patterns,
622 'create': True,
623 }
624 wait_wrapper(
625 client,
626 Exists,
627 wait_kwargs,
628 client.indices.put_index_template,
629 f_kwargs,
630 )
631 debug.lv3('Exiting function')
634def put_ilm(
635 client: 'Elasticsearch', name: str, policy: t.Union[t.Dict, None] = None
636) -> None:
637 """Publish an ILM Policy"""
638 debug.lv2('Starting function...')
639 try:
640 debug.lv4('TRY: ilm.put_lifecycle')
641 debug.lv5(f'ilm.put_lifecycle name: {name}, policy: {policy}')
642 res = client.ilm.put_lifecycle(name=name, policy=policy)
643 debug.lv5(f'ilm.put_lifecycle response: {res}')
644 except Exception as err:
645 msg = f'Unable to put ILM policy {name}. Error: {prettystr(err)}'
646 logger.error(msg)
647 raise TestbedFailure(msg) from err
648 debug.lv3('Exiting function')
651def resolver(client: 'Elasticsearch', name: str) -> dict:
652 """
653 Resolve details about the entity, be it an index, alias, or data_stream
655 Because you can pass search patterns and aliases as name, each element comes back
656 as an array:
658 {'indices': [], 'aliases': [], 'data_streams': []}
660 If you only resolve a single index or data stream, you will still have a 1-element
661 list
662 """
663 debug.lv2('Starting function...')
664 _ = client.indices.resolve_index(name=name, expand_wildcards=['open', 'closed'])
665 debug.lv3('Exiting function, returning value')
666 debug.lv5(f'Value = {_}')
667 return _
670def rollover(client: 'Elasticsearch', name: str) -> None:
671 """Rollover alias or data_stream identified by name"""
672 debug.lv2('Starting function...')
673 res = client.indices.rollover(alias=name, wait_for_active_shards='all')
674 debug.lv5(f'rollover response: {res}')
675 debug.lv3('Exiting function')
678def snapshot_name(client: 'Elasticsearch', name: str) -> t.Union[t.AnyStr, None]:
679 """Get the name of the snapshot behind the mounted index data"""
680 debug.lv2('Starting function...')
681 res = {}
682 if exists(client, 'index', name): # Can jump straight to nested keys if it exists
683 res = client.indices.get(index=name)[name]['settings']['index']
684 debug.lv5(f'indices.get response: {res}')
685 try:
686 debug.lv4("TRY: retval = res['store']['snapshot']['snapshot_name']")
687 retval = res['store']['snapshot']['snapshot_name']
688 except KeyError:
689 logger.error(f'{name} is not a searchable snapshot')
690 retval = None
691 debug.lv3('Exiting function, returning value')
692 debug.lv5(f'Value = {retval}')
693 return retval