Coverage for curator/cli_singletons/object_class.py: 81%
118 statements
« prev ^ index » next coverage.py v7.3.0, created at 2023-08-16 15:27 -0600
« prev ^ index » next coverage.py v7.3.0, created at 2023-08-16 15:27 -0600
1"""Object builder"""
2import logging
3import sys
4from voluptuous import Schema
5from curator import IndexList, SnapshotList
6from curator.actions import (
7 Alias, Allocation, Close, ClusterRouting, CreateIndex, DeleteIndices, DeleteSnapshots,
8 ForceMerge, IndexSettings, Open, Reindex, Replicas, Restore, Rollover, Shrink,
9 Snapshot
10)
11from curator.defaults.settings import snapshot_actions
12from curator.exceptions import ConfigurationError, NoIndices, NoSnapshots
13from curator.validators import SchemaCheck, filters, options
14from curator.utils import get_client, prune_nones, validate_filters
18CLASS_MAP = {
19 'alias' : Alias,
20 'allocation' : Allocation,
21 'close' : Close,
22 'cluster_routing' : ClusterRouting,
23 'create_index' : CreateIndex,
24 'delete_indices' : DeleteIndices,
25 'delete_snapshots' : DeleteSnapshots,
26 'forcemerge' : ForceMerge,
27 'index_settings' : IndexSettings,
28 'open' : Open,
29 'reindex' : Reindex,
30 'replicas' : Replicas,
31 'restore' : Restore,
32 'rollover': Rollover,
33 'shrink': Shrink,
34 'snapshot' : Snapshot,
35}
37EXCLUDED_OPTIONS = [
38 'ignore_empty_list', 'timeout_override',
39 'continue_if_exception', 'disable_action'
40]
42class cli_action():
43 """
44 Unified class for all CLI singleton actions
45 """
46 def __init__(self, action, client_args, option_dict, filter_list, ignore_empty_list, **kwargs):
47 self.logger = logging.getLogger('curator.cli_singletons.cli_action.' + action)
48 self.action = action
49 self.repository = kwargs['repository'] if 'repository' in kwargs else None
50 if action[:5] != 'show_': # Ignore CLASS_MAP for show_indices/show_snapshots
51 try:
52 self.action_class = CLASS_MAP[action]
53 except KeyError:
54 self.logger.critical('Action must be one of {0}'.format(list(CLASS_MAP.keys())))
55 self.check_options(option_dict)
56 else:
57 self.options = option_dict
58 # Extract allow_ilm_indices so it can be handled separately.
59 if 'allow_ilm_indices' in self.options:
60 self.allow_ilm = self.options.pop('allow_ilm_indices')
61 else:
62 self.allow_ilm = False
63 if action == 'alias':
64 self.alias = {
65 'name': option_dict['name'],
66 'extra_settings': option_dict['extra_settings'],
67 'wini': kwargs['warn_if_no_indices'] if 'warn_if_no_indices' in kwargs else False
68 }
69 for k in ['add', 'remove']:
70 if k in kwargs:
71 self.alias[k] = {}
72 self.check_filters(kwargs[k], loc='alias singleton', key=k)
73 self.alias[k]['filters'] = self.filters
74 if self.allow_ilm:
75 self.alias[k]['filters'].append({'filtertype':'ilm'})
76 elif action in ['cluster_routing', 'create_index', 'rollover']:
77 self.action_kwargs = {}
78 # No filters for these actions
79 if action == 'rollover':
80 # Ugh. Look how I screwed up here with args instead of kwargs,
81 # like EVERY OTHER ACTION seems to have... grr
82 # todo: fix this in Curator 6, as it's an API-level change.
83 self.action_args = (option_dict['name'], option_dict['conditions'])
84 for k in ['new_index', 'extra_settings', 'wait_for_active_shards']:
85 self.action_kwargs[k] = kwargs[k] if k in kwargs else None
86 else:
87 self.check_filters(filter_list)
88 self.client = get_client(**client_args)
89 self.ignore = ignore_empty_list
91 def prune_excluded(self, option_dict):
92 """Prune excluded options"""
93 for k in list(option_dict.keys()):
94 if k in EXCLUDED_OPTIONS:
95 del option_dict[k]
96 return option_dict
98 def check_options(self, option_dict):
99 """Validate provided options"""
100 try:
101 self.logger.debug('Validating provided options: {0}'.format(option_dict))
102 # Kludgy work-around to needing 'repository' in options for these actions
103 # but only to pass the schema check. It's removed again below.
104 if self.action in ['delete_snapshots', 'restore']:
105 option_dict['repository'] = self.repository
106 _ = SchemaCheck(
107 prune_nones(option_dict),
108 options.get_schema(self.action),
109 'options',
110 '{0} singleton action "options"'.format(self.action)
111 ).result()
112 self.options = self.prune_excluded(_)
113 # Remove this after the schema check, as the action class won't need it as an arg
114 if self.action in ['delete_snapshots', 'restore']:
115 del self.options['repository']
116 except ConfigurationError as err:
117 self.logger.critical('Unable to parse options: {0}'.format(err))
118 sys.exit(1)
120 def check_filters(self, filter_dict, loc='singleton', key='filters'):
121 """Validate provided filters"""
122 try:
123 self.logger.debug('Validating provided filters: {0}'.format(filter_dict))
124 _ = SchemaCheck(
125 filter_dict,
126 Schema(filters.Filters(self.action, location=loc)),
127 key,
128 '{0} singleton action "{1}"'.format(self.action, key)
129 ).result()
130 self.filters = validate_filters(self.action, _)
131 except ConfigurationError as err:
132 self.logger.critical('Unable to parse filters: {0}'.format(err))
133 sys.exit(1)
135 def do_filters(self):
136 """Actually run the filters"""
137 self.logger.debug('Running filters and testing for empty list object')
138 if self.allow_ilm:
139 self.filters.append({'filtertype':'ilm', 'exclude':True})
140 try:
141 self.list_object.iterate_filters({'filters':self.filters})
142 self.list_object.empty_list_check()
143 except (NoIndices, NoSnapshots) as err:
144 otype = 'index' if isinstance(err, NoIndices) else 'snapshot'
145 if self.ignore:
146 self.logger.info('Singleton action not performed: empty {0} list'.format(otype))
147 sys.exit(0)
148 else:
149 self.logger.error('Singleton action failed due to empty {0} list'.format(otype))
150 sys.exit(1)
152 def get_list_object(self):
153 """Get either a SnapshotList or IndexList object"""
154 if self.action in snapshot_actions() or self.action == 'show_snapshots':
155 self.list_object = SnapshotList(self.client, repository=self.repository)
156 else:
157 self.list_object = IndexList(self.client)
159 def get_alias_obj(self):
160 """Get the Alias object"""
161 action_obj = Alias(name=self.alias['name'], extra_settings=self.alias['extra_settings'])
162 for k in ['remove', 'add']:
163 if k in self.alias:
164 self.logger.debug(
165 '{0}ing matching indices {1} alias "{2}"'.format(
166 'Add' if k == 'add' else 'Remov', # 0 = "Add" or "Remov"
167 'to' if k == 'add' else 'from', # 1 = "to" or "from"
168 self.alias['name'] # 2 = the alias name
169 )
170 )
171 self.alias[k]['ilo'] = IndexList(self.client)
172 self.alias[k]['ilo'].iterate_filters({'filters':self.alias[k]['filters']})
173 fltr = getattr(action_obj, k)
174 fltr(self.alias[k]['ilo'], warn_if_no_indices=self.alias['wini'])
175 return action_obj
177 def do_singleton_action(self, dry_run=False):
178 """Execute the (ostensibly) completely ready to run action"""
179 self.logger.debug('Doing the singleton "{0}" action here.'.format(self.action))
180 try:
181 if self.action == 'alias':
182 action_obj = self.get_alias_obj()
183 elif self.action in ['cluster_routing', 'create_index', 'rollover']:
184 action_obj = self.action_class(self.client, *self.action_args, **self.action_kwargs)
185 else:
186 self.get_list_object()
187 self.do_filters()
188 self.logger.debug('OPTIONS = {0}'.format(self.options))
189 action_obj = self.action_class(self.list_object, **self.options)
190 try:
191 if dry_run:
192 action_obj.do_dry_run()
193 else:
194 action_obj.do_action()
195 except Exception as err:
196 raise err # pass it on?
197 except Exception as err:
198 self.logger.critical(
199 'Failed to complete action: {0}. {1}: {2}'.format(self.action, type(err), err))
200 sys.exit(1)
201 self.logger.info('"{0}" action completed.'.format(self.action))