Coverage for tests/conftest.py: 96%
236 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-05-02 08:55 -0600
« prev ^ index » next coverage.py v7.4.4, created at 2024-05-02 08:55 -0600
1"""Top-level conftest.py"""
3# pylint: disable=missing-function-docstring,redefined-outer-name,R0913
4import typing as t
5from os import environ
6from datetime import datetime, timezone
7import random
8import string
9import pytest
10from elasticsearch8.exceptions import NotFoundError
11from es_client import Builder
12from es_client.helpers.logging import set_logging
13from es_testbed.defaults import NAMEMAPPER
14from es_testbed.helpers.es_api import get_ds_current, get_write_index
16LOGLEVEL = 'DEBUG'
17LOCALREPO = 'testing'
20@pytest.fixture(scope='class')
21def actual_index(entitymgr):
22 def _actual_index(tb, which):
23 if tb.plan.type == 'data_stream':
24 return entitymgr(tb).ds.backing_indices[which]
25 return entitymgr(tb).entity_list[which].name # implied else
27 return _actual_index
30@pytest.fixture(scope='class')
31def actual_rollover(entitymgr):
32 def _actual_rollover(tb):
33 if tb.plan.type == 'data_stream':
34 return entitymgr(tb).last
35 if tb.plan.rollover_alias:
36 if entitymgr(tb).alias.name is not None: 36 ↛ 38line 36 didn't jump to line 38, because the condition on line 36 was never false
37 return entitymgr(tb).alias.name
38 return '' # implied else
40 return _actual_rollover
43@pytest.fixture(scope='class')
44def actual_write_index(actual_rollover):
45 def _actual_write_index(tb):
46 name = actual_rollover(tb)
47 if not name:
48 return name
49 func = get_write_index
50 if tb.plan.type == 'data_stream':
51 func = get_ds_current
52 return func(tb.client, name)
54 return _actual_write_index
57@pytest.fixture(scope='session')
58def client():
59 """Return an Elasticsearch client"""
60 host = environ.get('TEST_ES_SERVER')
61 user = environ.get('TEST_USER')
62 pswd = environ.get('TEST_PASS')
63 cacrt = environ.get('CA_CRT')
64 file = environ.get('ES_CLIENT_FILE', None) # Path to es_client YAML config
65 repo = environ.get('TEST_ES_REPO', 'found-snapshots')
66 if file: 66 ↛ 67line 66 didn't jump to line 67, because the condition on line 66 was never true
67 kwargs = {'configfile': file}
68 else:
69 kwargs = {
70 'configdict': {
71 'elasticsearch': {
72 'client': {'hosts': host, 'ca_certs': cacrt},
73 'other_settings': {'username': user, 'password': pswd},
74 }
75 }
76 }
77 set_logging({'loglevel': LOGLEVEL, 'blacklist': ['elastic_transport', 'urllib3']})
78 builder = Builder(**kwargs)
79 builder.connect()
80 if builder.client.license.get_trial_status()['eligible_to_start_trial']: 80 ↛ 83line 80 didn't jump to line 83, because the condition on line 80 was never false
81 builder.client.license.post_start_trial(acknowledge=True)
82 # This is a contradiction that cannot exist...
83 if repo == 'found-snapshots' and host == 'https://127.0.0.1:9200' and not file: 83 ↛ 87line 83 didn't jump to line 87, because the condition on line 83 was never false
84 # We'll make our own and set the ENV var
85 create_repository(builder.client, LOCALREPO)
86 environ['TEST_ES_REPO'] = LOCALREPO
87 return builder.client
90@pytest.fixture(scope='class')
91def cold():
92 """Return the prefix for cold indices"""
93 return 'restored-'
96@pytest.fixture(scope='class')
97def components(namecore):
98 """Return the component names in a list"""
99 components = []
100 components.append(f'{namecore("component")}-000001')
101 components.append(f'{namecore("component")}-000002')
102 return components
105def create_repository(client, name: str) -> None:
106 """
107 PUT _snapshot/REPO_NAME
108 {
109 "type": "fs",
110 "settings": {
111 "location": "RELATIVE_PATH"
112 }
113 }
114 """
115 repobody = {'type': 'fs', 'settings': {'location': '/media'}}
116 client.snapshot.create_repository(name=name, repository=repobody, verify=False)
119@pytest.fixture(scope='class')
120def entity_count(defaults):
121 def _entity_count(kind):
122 if kind == 'data_stream':
123 return 1
124 return defaults()['entity_count']
126 return _entity_count
129@pytest.fixture(scope='class')
130def defaults() -> t.Dict:
131 def _defaults(sstier: str = 'hot') -> t.Dict:
132 retval = {'entity_count': 3, 'docs': 10, 'match': True, 'searchable': None}
133 if sstier in ['cold', 'frozen']:
134 retval['searchable'] = sstier
135 return retval
137 return _defaults
140@pytest.fixture(scope='class')
141def entitymgr():
142 def _entitymgr(tb):
143 if tb.plan.type == 'data_stream':
144 return tb.data_streammgr
145 return tb.indexmgr # implied else
147 return _entitymgr
150@pytest.fixture(scope='class')
151def first():
152 return 0
155@pytest.fixture(scope='class')
156def frozen():
157 """Return the prefix for frozen indices"""
158 return 'partial-'
161@pytest.fixture(scope='class')
162def get_template(template):
163 def _get_template(client):
164 return client.indices.get_index_template(name=template)['index_templates']
166 return _get_template
169@pytest.fixture(scope='class')
170def idxmain(namecore, ymd):
171 def _idxmain(kind):
172 result = f'{namecore(kind)}'
173 if kind == 'data_stream':
174 return f'.ds-{result}-{ymd}'
175 return result
177 return _idxmain
180@pytest.fixture(scope='class')
181def idxss(first, ssprefix, rollable):
182 def _idxss(tier, which, plan):
183 if which != first:
184 if rollable(plan):
185 return '' # No searchable prefix
186 return ssprefix(tier)
188 return _idxss
191@pytest.fixture(scope='class')
192def idxtail(first, last):
193 def _idxtail(which):
194 if which == first:
195 return '-000001'
196 if which == last: 196 ↛ 198line 196 didn't jump to line 198, because the condition on line 196 was never false
197 return '-000003'
198 return '-000002' # implied else
200 return _idxtail
203@pytest.fixture(scope='class')
204def index_name(first, idxmain, idxss, idxtail):
205 def _index_name(which=first, plan=None, tier: str = 'hot'):
206 prefix = idxss(tier, which, plan)
207 main = idxmain(plan.type)
208 suffix = idxtail(which)
209 return f'{prefix}{main}{suffix}'
211 return _index_name
214@pytest.fixture(scope='class')
215def last():
216 return -1
219@pytest.fixture(scope='class')
220def namecore(prefix, uniq):
221 def _namecore(kind):
222 if kind == 'indices':
223 return f'{prefix}-{NAMEMAPPER["index"]}-{uniq}'
224 return f'{prefix}-{NAMEMAPPER[kind]}-{uniq}'
226 return _namecore
229@pytest.fixture(scope='class')
230def prefix():
231 """Return a random prefix"""
232 return randomstr(length=8, lowercase=True)
235def randomstr(length: int = 16, lowercase: bool = False):
236 """Generate a random string"""
237 letters = string.ascii_uppercase
238 if lowercase: 238 ↛ 240line 238 didn't jump to line 240, because the condition on line 238 was never false
239 letters = string.ascii_lowercase
240 return str(''.join(random.choices(letters + string.digits, k=length)))
243@pytest.fixture(scope='class')
244def repo(client):
245 """Return the elasticsearch repository"""
246 name = environ.get('TEST_ES_REPO', 'found-snapshots') # Going with Cloud default
247 if not repo: 247 ↛ 248line 247 didn't jump to line 248, because the condition on line 247 was never true
248 return False
249 try:
250 client.snapshot.get_repository(name=name)
251 except NotFoundError:
252 return False
253 return name # Return the repo name if it's online
256@pytest.fixture(scope='class')
257def rollable():
258 def _rollable(plan):
259 if plan.type == 'data_stream':
260 return True
261 if plan.rollover_alias:
262 return True
263 return False
265 return _rollable
268@pytest.fixture(scope='class')
269def rollovername(namecore, rollable):
270 def _rollovername(plan):
271 if rollable(plan):
272 if plan.type == 'data_stream':
273 return namecore(plan.type)
274 return namecore('index')
275 return ''
277 return _rollovername
280@pytest.fixture(scope='class')
281def settings(defaults, prefix, repo, uniq):
282 def _settings(
283 plan_type: t.Literal['data_stream', 'index'] = 'data_stream',
284 rollover_alias: bool = False,
285 ilm: t.Union[t.Dict, False] = False,
286 sstier: str = 'hot',
287 ):
288 return {
289 'type': plan_type,
290 'prefix': prefix,
291 'rollover_alias': rollover_alias,
292 'repository': repo,
293 'uniq': uniq,
294 'ilm': ilm,
295 'defaults': defaults(sstier),
296 }
298 return _settings
301@pytest.fixture(scope='class')
302def skip_no_repo(repo) -> None:
303 def _skip_no_repo(skip_it: bool) -> None:
304 if skip_it:
305 if not repo: 305 ↛ 306line 305 didn't jump to line 306, because the condition on line 305 was never true
306 pytest.skip('No snapshot repository', allow_module_level=True)
308 return _skip_no_repo
311@pytest.fixture(scope='class')
312def skip_localhost() -> None:
313 def _skip_localhost(skip_it: bool) -> None:
314 if skip_it:
315 host = environ.get('TEST_ES_SERVER')
316 file = environ.get('ES_CLIENT_FILE', None) # Path to es_client YAML config
317 repo = environ.get('TEST_ES_REPO')
318 if repo == LOCALREPO and host == 'https://127.0.0.1:9200' and not file: 318 ↛ exitline 318 didn't return from function '_skip_localhost', because the condition on line 318 was never false
319 pytest.skip(
320 'Local Docker test does not work with this test',
321 allow_module_level=False,
322 )
324 return _skip_localhost
327@pytest.fixture(scope='class')
328def ssprefix(cold, frozen):
329 def _ssprefix(tier):
330 retval = '' # hot or warm
331 if tier == 'cold':
332 retval = cold
333 if tier == 'frozen':
334 retval = frozen
335 return retval
337 return _ssprefix
340@pytest.fixture(scope='class')
341def template(namecore):
342 """Return the name of the index template"""
343 return f'{namecore("template")}-000001'
346@pytest.fixture(scope='class')
347def uniq():
348 """Return a random uniq value"""
349 return randomstr(length=8, lowercase=True)
352@pytest.fixture(scope='class')
353def write_index_name(last, idxmain, idxss, idxtail, rollable):
354 def _write_index_name(which=last, plan=None, tier: str = 'hot'):
355 if not rollable(plan):
356 return ''
357 prefix = idxss(tier, which, plan)
358 main = idxmain(plan.type)
359 suffix = idxtail(which)
360 return f'{prefix}{main}{suffix}'
362 return _write_index_name
365@pytest.fixture(scope='class')
366def ymd():
367 return datetime.now(timezone.utc).strftime('%Y.%m.%d')