Coverage for /Users/buh/.pyenv/versions/3.12.9/envs/es-testbed/lib/python3.12/site-packages/es_testbed/_plan.py: 100%
87 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"""TestPlan Class Definition"""
3import typing as t
4import logging
5from dotmap import DotMap
6import tiered_debug as debug
7from es_testbed.defaults import TESTPLAN
8from es_testbed.helpers.utils import build_ilm_policy, prettystr, randomstr
10logger = logging.getLogger(__name__)
13class PlanBuilder:
14 """
15 Plan builder class
17 """
19 def __init__(
20 self,
21 settings: t.Dict,
22 autobuild: t.Optional[bool] = True,
23 ):
24 """Class initializer
26 Receives a settings dictionary and builds the object based on it. This
27 dictionary is converted into a DotMap object to make it easier to access
28 and modify the settings.
30 The settings dictionary should have the following structure:
32 .. code-block:: python
34 {
35 'type': 'indices', # indices or data_streams
36 'prefix': 'es-testbed', # The default prefix for everything we create
37 'rollover_alias': False, # Only respected if 'type' == 'indices'.
38 # Will rollover after creation and filling 1st
39 # If True, will be overridden to value of alias
40 # If False, will be overridden with None
41 'uniq': 'my-unique-str', # If not provided, randomstr()
42 'repository': # Only used for cold/frozen tier for snapshots
43 'ilm': { # All of these ILM values are defaults
44 'enabled': False,
45 'phases': ['hot', 'delete'],
46 'readonly': PHASE # Define readonly action during named PHASE
47 'forcemerge': False,
48 'max_num_segments': 1,
49 'policy': {} # Define full ILM policy in advance.
50 },
51 'index_buildlist': [], # List of indices to create
52 }
54 The index_buildlist is a list of dictionaries, each with a similar structure:
56 .. code-block:: python
58 'index_buildlist': [
59 {
60 'preset': 'NAME', # docgen_preset name, included or otherwise
61 'options': { # kwargs for the generator function
62 'docs': 10,
63 'start_at': 0,
64 'match': True,
65 }
66 'target_tier': 'frozen' # Target tier for 1st (oldest) index created
67 },
68 {
69 'preset': 'NAME', # docgen_preset name, included or otherwise
70 'options': { # kwargs for the generator function
71 'docs': 10,
72 'start_at': 10,
73 'match': True,
74 }
75 'target_tier': 'cold' # Target tier for 2nd index created
76 },
77 {
78 'preset': 'NAME', # docgen_preset name, included or otherwise
79 'options': { # kwargs for the generator function
80 'docs': 10,
81 'start_at': 20,
82 'match': True,
83 }
84 'target_tier': 'hot' # Target tier for last (newest) index created
85 },
86 ]
88 The index_buildlist can be provided after the object is created, and is,
89 in fact, if using a preset, which will provide the entire plan, including
90 the index_buildlist, and the values that need to go in the indices.
92 Args:
93 settings (t.Dict): The settings dictionary
94 autobuild (t.Optional[bool], optional): Whether to autobuild the plan.
95 Defaults to True.
97 Raises:
98 ValueError: Must provide a settings dictionary
99 """
100 debug.lv2('Initializing PlanBuilder object...')
101 if settings is None:
102 raise ValueError('Must provide a settings dictionary')
103 self.settings = settings
104 debug.lv5(f'SETTINGS: {settings}')
105 self._plan = DotMap(TESTPLAN)
106 debug.lv5(f'INITIAL PLAN: {prettystr(self._plan)}')
107 self._plan.cleanup = 'UNSET' # Future use?
108 if autobuild:
109 self.setup()
110 debug.lv3('PlanBuilder object initialized')
112 @property
113 def plan(self) -> DotMap:
114 """Return the Plan"""
115 return self._plan
117 def _create_lists(self) -> None:
118 debug.lv2('Starting method...')
119 names = [
120 'indices',
121 'data_stream',
122 'snapshots',
123 'ilm_policies',
124 'index_templates',
125 'component_templates',
126 ]
127 for name in names:
128 self._plan[name] = []
129 debug.lv3('Exiting method')
131 def setup(self) -> None:
132 """Do initial setup of the Plan DotMap"""
133 debug.lv2('Starting method...')
134 self._plan.uniq = randomstr(length=8, lowercase=True)
135 self._create_lists()
136 self.update(self.settings) # Override with settings.
137 self.update_rollover_alias()
138 debug.lv3('Rollover alias updated')
139 self.update_ilm()
140 debug.lv5(f'FINAL PLAN: {prettystr(self._plan.toDict())}')
141 debug.lv3('Exiting method')
143 def update(self, settings: t.Dict) -> None:
144 """Update the Plan DotMap"""
145 debug.lv2('Starting method...')
146 self._plan.update(DotMap(settings))
147 debug.lv5(f'Updated plan: {prettystr(self._plan.toDict())}')
148 debug.lv3('Exiting method')
150 def update_ilm(self) -> None:
151 """Update the ILM portion of the Plan DotMap"""
152 debug.lv2('Starting method...')
153 setdefault = False
154 if 'ilm' not in self._plan:
155 debug.lv3('key "ilm" is not in plan')
156 setdefault = True
157 if isinstance(self._plan.ilm, dict):
158 _ = DotMap(self._plan.ilm)
159 self._plan.ilm = _
160 if isinstance(self._plan.ilm, DotMap):
161 if 'enabled' not in self._plan.ilm:
162 # Override with defaults
163 debug.lv3(
164 'plan.ilm does not have key "enabled". Overriding with defaults'
165 )
166 setdefault = True
167 elif isinstance(self._plan.ilm, bool):
168 if self._plan.ilm:
169 logger.warning(
170 '"plan.ilm: True" is incorrect. Use plan.ilm.enabled: True'
171 )
172 debug.lv3('plan.ilm is boolean. Overriding with defaults')
173 setdefault = True
174 if setdefault:
175 debug.lv3('Setting defaults for ILM')
176 self._plan.ilm = DotMap(TESTPLAN['ilm'])
177 if self._plan.ilm.enabled:
178 ilm = self._plan.ilm
179 if not isinstance(self._plan.ilm.phases, list):
180 logger.error('Phases is not a list!')
181 self._plan.ilm.phases = TESTPLAN['ilm']['phases']
182 for entity in self._plan.index_buildlist:
183 if 'searchable' in entity and entity['searchable'] is not None:
184 if not entity['searchable'] in ilm.phases:
185 ilm.phases.append(entity['searchable'])
186 debug.lv5(f'ILM = {ilm}')
187 debug.lv5(f'self._plan.ilm = {self._plan.ilm}')
188 kwargs = {
189 'phases': ilm.phases,
190 'forcemerge': ilm.forcemerge,
191 'max_num_segments': ilm.max_num_segments,
192 'readonly': ilm.readonly,
193 'repository': self._plan.repository,
194 }
195 debug.lv5(f'KWARGS = {kwargs}')
196 self._plan.ilm.policy = build_ilm_policy(**kwargs)
197 debug.lv3('Exiting method')
199 def update_rollover_alias(self) -> None:
200 """Update the Rollover Alias value"""
201 debug.lv2('Starting method...')
202 if self._plan.rollover_alias:
203 self._plan.rollover_alias = f'{self._plan.prefix}-idx-{self._plan.uniq}'
204 else:
205 self._plan.rollover_alias = None
206 debug.lv5(f'Updated rollover_alias = {self._plan.rollover_alias}')
207 debug.lv3('Exiting method')