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

1"""Functions that make Elasticsearch API Calls""" 

2 

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) 

24 

25if t.TYPE_CHECKING: 

26 from elasticsearch8 import Elasticsearch 

27 

28PAUSE_VALUE = float(getenv(PAUSE_ENVVAR, default=PAUSE_DEFAULT)) 

29 

30logger = logging.getLogger(__name__) 

31 

32 

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] 

90 

91 

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') 

107 

108 

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') 

149 

150 

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') 

163 

164 

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 

194 

195 

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 

213 

214 

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 

258 

259 

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) 

271 

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') 

290 

291 

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 

334 

335 

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 

344 

345 :param client: ES client 

346 :param name: Index name 

347 :param doc_generator: The generator function 

348 

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') 

359 

360 

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 

375 

376 

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') 

387 

388 

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 

427 

428 

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 

443 

444 

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 

463 

464 

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 

478 

479 

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 

495 

496 

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 

513 

514 

515def get_write_index(client: 'Elasticsearch', name: str) -> str: 

516 """ 

517 Calls :py:meth:`~.elasticsearch.client.IndicesClient.get_alias` 

518 

519 :param client: A client connection object 

520 :param name: An alias name 

521 

522 :type client: :py:class:`~.elasticsearch.Elasticsearch` 

523 

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 

542 

543 

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 

569 

570 

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') 

590 

591 

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') 

605 

606 

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') 

632 

633 

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') 

649 

650 

651def resolver(client: 'Elasticsearch', name: str) -> dict: 

652 """ 

653 Resolve details about the entity, be it an index, alias, or data_stream 

654 

655 Because you can pass search patterns and aliases as name, each element comes back 

656 as an array: 

657 

658 {'indices': [], 'aliases': [], 'data_streams': []} 

659 

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 _ 

668 

669 

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') 

676 

677 

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