Coverage for curator/cli_singletons/object_class.py: 81%

1"""Object builder"""

2 import logging

3 import sys

4 from voluptuous import Schema

5 from curator import IndexList, SnapshotList

6 from curator.actions import (

7 Alias, Allocation, Close, ClusterRouting, CreateIndex, DeleteIndices, DeleteSnapshots,

8 ForceMerge, IndexSettings, Open, Reindex, Replicas, Restore, Rollover, Shrink,

9 Snapshot

10)

11 from curator.defaults.settings import snapshot_actions

12 from curator.exceptions import ConfigurationError, NoIndices, NoSnapshots

13 from curator.validators import SchemaCheck, filters, options

14 from curator.utils import get_client, prune_nones, validate_filters

15

16

17

18 CLASS_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}

36

37 EXCLUDED_OPTIONS = [

38 'ignore_empty_list', 'timeout_override',

39 'continue_if_exception', 'disable_action'

40]

41

42 class 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

90

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

97

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)

119

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)

134

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)

151

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)

158

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

176

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