6262import java .util .function .BiConsumer ;
6363import java .util .function .Consumer ;
6464import java .util .function .Predicate ;
65+ import java .util .function .Supplier ;
6566import java .util .regex .Pattern ;
6667import java .util .stream .Collectors ;
6768
7273import static org .elasticsearch .xpack .core .ClientHelper .executeAsyncWithOrigin ;
7374
7475/**
75- * Manages the lifecycle of a single index, its template, mapping and and data upgrades/migrations.
76+ * Manages the lifecycle of a single index, mapping and and data upgrades/migrations.
7677 */
7778public class SecurityIndexManager implements ClusterStateListener {
7879
@@ -82,28 +83,41 @@ public class SecurityIndexManager implements ClusterStateListener {
8283 public static final String TEMPLATE_VERSION_PATTERN = Pattern .quote ("${security.template.version}" );
8384 public static final String SECURITY_TEMPLATE_NAME = "security-index-template" ;
8485 public static final String SECURITY_INDEX_NAME = ".security" ;
85- private static final Logger LOGGER = LogManager .getLogger (SecurityIndexManager .class );
86+ private static final Logger logger = LogManager .getLogger (SecurityIndexManager .class );
8687
87- private final String indexName ;
88+ private final String aliasName ;
89+ private final String internalIndexName ;
90+ private final int internalIndexFormat ;
91+ private final Supplier <byte []> mappingSourceSupplier ;
8892 private final Client client ;
8993
9094 private final List <BiConsumer <State , State >> stateChangeListeners = new CopyOnWriteArrayList <>();
9195
9296 private volatile State indexState ;
9397
94- public SecurityIndexManager (Client client , String indexName , ClusterService clusterService ) {
95- this (client , indexName , State .UNRECOVERED_STATE );
98+ public static SecurityIndexManager buildSecurityIndexManager (Client client , ClusterService clusterService ) {
99+ return new SecurityIndexManager (client , SECURITY_INDEX_NAME , INTERNAL_SECURITY_INDEX , INTERNAL_INDEX_FORMAT ,
100+ SecurityIndexManager ::readSecurityTemplateAsBytes , clusterService );
101+ }
102+
103+ private SecurityIndexManager (Client client , String aliasName , String internalIndexName , int internalIndexFormat ,
104+ Supplier <byte []> mappingSourceSupplier , ClusterService clusterService ) {
105+ this (client , aliasName , internalIndexName , internalIndexFormat , mappingSourceSupplier , State .UNRECOVERED_STATE );
96106 clusterService .addListener (this );
97107 }
98108
99- private SecurityIndexManager (Client client , String indexName , State indexState ) {
100- this .client = client ;
101- this .indexName = indexName ;
109+ private SecurityIndexManager (Client client , String aliasName , String internalIndexName , int internalIndexFormat ,
110+ Supplier <byte []> mappingSourceSupplier , State indexState ) {
111+ this .aliasName = aliasName ;
112+ this .internalIndexName = internalIndexName ;
113+ this .internalIndexFormat = internalIndexFormat ;
114+ this .mappingSourceSupplier = mappingSourceSupplier ;
102115 this .indexState = indexState ;
116+ this .client = client ;
103117 }
104118
105119 public SecurityIndexManager freeze () {
106- return new SecurityIndexManager (null , indexName , indexState );
120+ return new SecurityIndexManager (null , aliasName , internalIndexName , internalIndexFormat , mappingSourceSupplier , indexState );
107121 }
108122
109123 public boolean checkMappingVersion (Predicate <Version > requiredVersion ) {
@@ -143,9 +157,10 @@ public ElasticsearchException getUnavailableReason() {
143157 }
144158
145159 if (localState .indexExists ) {
146- return new UnavailableShardsException (null , "at least one primary shard for the security index is unavailable" );
160+ return new UnavailableShardsException (null ,
161+ "at least one primary shard for the index [" + localState .concreteIndexName + "] is unavailable" );
147162 } else {
148- return new IndexNotFoundException (SECURITY_INDEX_NAME );
163+ return new IndexNotFoundException (localState . concreteIndexName );
149164 }
150165 }
151166
@@ -163,20 +178,20 @@ public void clusterChanged(ClusterChangedEvent event) {
163178 if (event .state ().blocks ().hasGlobalBlock (GatewayService .STATE_NOT_RECOVERED_BLOCK )) {
164179 // wait until the gateway has recovered from disk, otherwise we think we don't have the
165180 // .security index but they may not have been restored from the cluster state on disk
166- LOGGER .debug ("security index manager waiting until state has been recovered" );
181+ logger .debug ("security index manager waiting until state has been recovered" );
167182 return ;
168183 }
169184 final State previousState = indexState ;
170- final IndexMetaData indexMetaData = resolveConcreteIndex (indexName , event .state ().metaData ());
185+ final IndexMetaData indexMetaData = resolveConcreteIndex (aliasName , event .state ().metaData ());
171186 final boolean indexExists = indexMetaData != null ;
172187 final boolean isIndexUpToDate = indexExists == false ||
173- INDEX_FORMAT_SETTING .get (indexMetaData .getSettings ()).intValue () == INTERNAL_INDEX_FORMAT ;
188+ INDEX_FORMAT_SETTING .get (indexMetaData .getSettings ()).intValue () == internalIndexFormat ;
174189 final boolean indexAvailable = checkIndexAvailable (event .state ());
175190 final boolean mappingIsUpToDate = indexExists == false || checkIndexMappingUpToDate (event .state ());
176191 final Version mappingVersion = oldestIndexMappingVersion (event .state ());
177192 final ClusterHealthStatus indexStatus = indexMetaData == null ? null :
178193 new ClusterIndexHealth (indexMetaData , event .state ().getRoutingTable ().index (indexMetaData .getIndex ())).getStatus ();
179- final String concreteIndexName = indexMetaData == null ? INTERNAL_SECURITY_INDEX : indexMetaData .getIndex ().getName ();
194+ final String concreteIndexName = indexMetaData == null ? internalIndexName : indexMetaData .getIndex ().getName ();
180195 final State newState = new State (indexExists , isIndexUpToDate , indexAvailable , mappingIsUpToDate , mappingVersion , concreteIndexName ,
181196 indexStatus );
182197 this .indexState = newState ;
@@ -193,61 +208,55 @@ private boolean checkIndexAvailable(ClusterState state) {
193208 if (routingTable != null && routingTable .allPrimaryShardsActive ()) {
194209 return true ;
195210 }
196- LOGGER .debug ("Security index [{}] is not yet active" , indexName );
211+ logger .debug ("Index [{}] is not yet active" , aliasName );
197212 return false ;
198213 }
199214
200215 /**
201216 * Returns the routing-table for this index, or <code>null</code> if the index does not exist.
202217 */
203218 private IndexRoutingTable getIndexRoutingTable (ClusterState clusterState ) {
204- IndexMetaData metaData = resolveConcreteIndex (indexName , clusterState .metaData ());
219+ IndexMetaData metaData = resolveConcreteIndex (aliasName , clusterState .metaData ());
205220 if (metaData == null ) {
206221 return null ;
207222 } else {
208223 return clusterState .routingTable ().index (metaData .getIndex ());
209224 }
210225 }
211226
212- public static boolean checkTemplateExistsAndVersionMatches (
213- String templateName , ClusterState state , Logger logger , Predicate <Version > predicate ) {
214-
215- return TemplateUtils .checkTemplateExistsAndVersionMatches (templateName , SECURITY_VERSION_STRING ,
216- state , logger , predicate );
227+ public static boolean checkTemplateExistsAndVersionMatches (String templateName , ClusterState state , Logger logger ,
228+ Predicate <Version > predicate ) {
229+ return TemplateUtils .checkTemplateExistsAndVersionMatches (templateName , SECURITY_VERSION_STRING , state , logger , predicate );
217230 }
218231
219232 private boolean checkIndexMappingUpToDate (ClusterState clusterState ) {
220233 return checkIndexMappingVersionMatches (clusterState , Version .CURRENT ::equals );
221234 }
222235
223- private boolean checkIndexMappingVersionMatches (ClusterState clusterState ,
224- Predicate <Version > predicate ) {
225- return checkIndexMappingVersionMatches (indexName , clusterState , LOGGER , predicate );
236+ private boolean checkIndexMappingVersionMatches (ClusterState clusterState , Predicate <Version > predicate ) {
237+ return checkIndexMappingVersionMatches (aliasName , clusterState , logger , predicate );
226238 }
227239
228- public static boolean checkIndexMappingVersionMatches (String indexName ,
229- ClusterState clusterState , Logger logger ,
240+ public static boolean checkIndexMappingVersionMatches (String indexName , ClusterState clusterState , Logger logger ,
230241 Predicate <Version > predicate ) {
231- return loadIndexMappingVersions (indexName , clusterState , logger )
232- .stream ().allMatch (predicate );
242+ return loadIndexMappingVersions (indexName , clusterState , logger ).stream ().allMatch (predicate );
233243 }
234244
235245 private Version oldestIndexMappingVersion (ClusterState clusterState ) {
236- final Set <Version > versions = loadIndexMappingVersions (indexName , clusterState , LOGGER );
246+ final Set <Version > versions = loadIndexMappingVersions (aliasName , clusterState , logger );
237247 return versions .stream ().min (Version ::compareTo ).orElse (null );
238248 }
239249
240- private static Set <Version > loadIndexMappingVersions (String indexName ,
241- ClusterState clusterState , Logger logger ) {
250+ private static Set <Version > loadIndexMappingVersions (String aliasName , ClusterState clusterState , Logger logger ) {
242251 Set <Version > versions = new HashSet <>();
243- IndexMetaData indexMetaData = resolveConcreteIndex (indexName , clusterState .metaData ());
252+ IndexMetaData indexMetaData = resolveConcreteIndex (aliasName , clusterState .metaData ());
244253 if (indexMetaData != null ) {
245254 for (Object object : indexMetaData .getMappings ().values ().toArray ()) {
246255 MappingMetaData mappingMetaData = (MappingMetaData ) object ;
247256 if (mappingMetaData .type ().equals (MapperService .DEFAULT_MAPPING )) {
248257 continue ;
249258 }
250- versions .add (readMappingVersion (indexName , mappingMetaData , logger ));
259+ versions .add (readMappingVersion (aliasName , mappingMetaData , logger ));
251260 }
252261 }
253262 return versions ;
@@ -270,8 +279,7 @@ private static IndexMetaData resolveConcreteIndex(final String indexOrAliasName,
270279 return null ;
271280 }
272281
273- private static Version readMappingVersion (String indexName , MappingMetaData mappingMetaData ,
274- Logger logger ) {
282+ private static Version readMappingVersion (String indexName , MappingMetaData mappingMetaData , Logger logger ) {
275283 try {
276284 Map <String , Object > meta =
277285 (Map <String , Object >) mappingMetaData .sourceAsMap ().get ("_meta" );
@@ -289,17 +297,17 @@ private static Version readMappingVersion(String indexName, MappingMetaData mapp
289297 }
290298
291299 /**
292- * Validates the security index is up to date and does not need to migrated. If it is not, the
293- * consumer is called with an exception. If the security index is up to date, the runnable will
300+ * Validates that the index is up to date and does not need to be migrated. If it is not, the
301+ * consumer is called with an exception. If the index is up to date, the runnable will
294302 * be executed. <b>NOTE:</b> this method does not check the availability of the index; this check
295303 * is left to the caller so that this condition can be handled appropriately.
296304 */
297305 public void checkIndexVersionThenExecute (final Consumer <Exception > consumer , final Runnable andThen ) {
298306 final State indexState = this .indexState ; // use a local copy so all checks execute against the same state!
299307 if (indexState .indexExists && indexState .isIndexUpToDate == false ) {
300308 consumer .accept (new IllegalStateException (
301- "Security index is not on the current version. Security features relying on the index will not be available until " +
302- " the upgrade API is run on the security index" ));
309+ "Index [" + indexState . concreteIndexName + "] is not on the current version. Security features relying on the index"
310+ + " will not be available until the upgrade API is run on the index" ));
303311 } else {
304312 andThen .run ();
305313 }
@@ -313,17 +321,20 @@ public void prepareIndexIfNeededThenExecute(final Consumer<Exception> consumer,
313321 final State indexState = this .indexState ; // use a local copy so all checks execute against the same state!
314322 // TODO we should improve this so we don't fire off a bunch of requests to do the same thing (create or update mappings)
315323 if (indexState == State .UNRECOVERED_STATE ) {
316- consumer .accept (new ElasticsearchStatusException ("Cluster state has not been recovered yet, cannot write to the security index" ,
324+ consumer .accept (new ElasticsearchStatusException (
325+ "Cluster state has not been recovered yet, cannot write to the [" + indexState .concreteIndexName + "] index" ,
317326 RestStatus .SERVICE_UNAVAILABLE ));
318327 } else if (indexState .indexExists && indexState .isIndexUpToDate == false ) {
319328 consumer .accept (new IllegalStateException (
320- "Security index is not on the current version. Security features relying on the index will not be available until " +
321- " the upgrade API is run on the security index" ));
329+ "Index [" + indexState . concreteIndexName + "] is not on the current version."
330+ + "Security features relying on the index will not be available until the upgrade API is run on the index" ));
322331 } else if (indexState .indexExists == false ) {
323- LOGGER .info ("security index does not exist. Creating [{}] with alias [{}]" , INTERNAL_SECURITY_INDEX , SECURITY_INDEX_NAME );
324- Tuple <String , Settings > mappingAndSettings = loadMappingAndSettingsSourceFromTemplate ();
325- CreateIndexRequest request = new CreateIndexRequest (INTERNAL_SECURITY_INDEX )
326- .alias (new Alias (SECURITY_INDEX_NAME ))
332+ assert indexState .concreteIndexName != null ;
333+ logger .info ("security index does not exist. Creating [{}] with alias [{}]" , indexState .concreteIndexName , this .aliasName );
334+ final byte [] mappingSource = mappingSourceSupplier .get ();
335+ final Tuple <String , Settings > mappingAndSettings = parseMappingAndSettingsFromTemplateBytes (mappingSource );
336+ CreateIndexRequest request = new CreateIndexRequest (indexState .concreteIndexName )
337+ .alias (new Alias (this .aliasName ))
327338 .mapping (MapperService .SINGLE_MAPPING_NAME , mappingAndSettings .v1 (), XContentType .JSON )
328339 .waitForActiveShards (ActiveShardCount .ALL )
329340 .settings (mappingAndSettings .v2 ());
@@ -351,11 +362,11 @@ public void onFailure(Exception e) {
351362 }
352363 }, client .admin ().indices ()::create );
353364 } else if (indexState .mappingUpToDate == false ) {
354- LOGGER .info (
355- "security index [{}] (alias [{}]) is not up to date. Updating mapping" , indexState . concreteIndexName , SECURITY_INDEX_NAME );
356-
365+ logger .info ("Index [{}] (alias [{}]) is not up to date. Updating mapping" , indexState . concreteIndexName , this . aliasName );
366+ final byte [] mappingSource = mappingSourceSupplier . get ( );
367+ final Tuple < String , Settings > mappingAndSettings = parseMappingAndSettingsFromTemplateBytes ( mappingSource );
357368 PutMappingRequest request = new PutMappingRequest (indexState .concreteIndexName )
358- .source (loadMappingAndSettingsSourceFromTemplate () .v1 (), XContentType .JSON )
369+ .source (mappingAndSettings .v1 (), XContentType .JSON )
359370 .type (MapperService .SINGLE_MAPPING_NAME );
360371 executeAsyncWithOrigin (client .threadPool ().getThreadContext (), SECURITY_ORIGIN , request ,
361372 ActionListener .<AcknowledgedResponse >wrap (putMappingResponse -> {
@@ -370,11 +381,28 @@ public void onFailure(Exception e) {
370381 }
371382 }
372383
373- private Tuple <String , Settings > loadMappingAndSettingsSourceFromTemplate () {
374- final byte [] template = TemplateUtils .loadTemplate ("/" + SECURITY_TEMPLATE_NAME + ".json" , Version .CURRENT .toString (),
384+ /**
385+ * Return true if the state moves from an unhealthy ("RED") index state to a healthy ("non-RED") state.
386+ */
387+ public static boolean isMoveFromRedToNonRed (State previousState , State currentState ) {
388+ return (previousState .indexStatus == null || previousState .indexStatus == ClusterHealthStatus .RED )
389+ && currentState .indexStatus != null && currentState .indexStatus != ClusterHealthStatus .RED ;
390+ }
391+
392+ /**
393+ * Return true if the state moves from the index existing to the index not existing.
394+ */
395+ public static boolean isIndexDeleted (State previousState , State currentState ) {
396+ return previousState .indexStatus != null && currentState .indexStatus == null ;
397+ }
398+
399+ private static byte [] readSecurityTemplateAsBytes () {
400+ return TemplateUtils .loadTemplate ("/" + SECURITY_TEMPLATE_NAME + ".json" , Version .CURRENT .toString (),
375401 SecurityIndexManager .TEMPLATE_VERSION_PATTERN ).getBytes (StandardCharsets .UTF_8 );
376- final PutIndexTemplateRequest request = new PutIndexTemplateRequest ( SECURITY_TEMPLATE_NAME ). source ( template , XContentType . JSON );
402+ }
377403
404+ private static Tuple <String , Settings > parseMappingAndSettingsFromTemplateBytes (byte [] template ) {
405+ final PutIndexTemplateRequest request = new PutIndexTemplateRequest ("name_is_not_important" ).source (template , XContentType .JSON );
378406 final String mappingSource = request .mappings ().get (MapperService .SINGLE_MAPPING_NAME );
379407 try (XContentParser parser = XContentType .JSON .xContent ().createParser (NamedXContentRegistry .EMPTY ,
380408 DeprecationHandler .THROW_UNSUPPORTED_OPERATION , mappingSource )) {
@@ -391,21 +419,6 @@ private Tuple<String, Settings> loadMappingAndSettingsSourceFromTemplate() {
391419 }
392420 }
393421
394- /**
395- * Return true if the state moves from an unhealthy ("RED") index state to a healthy ("non-RED") state.
396- */
397- public static boolean isMoveFromRedToNonRed (State previousState , State currentState ) {
398- return (previousState .indexStatus == null || previousState .indexStatus == ClusterHealthStatus .RED )
399- && currentState .indexStatus != null && currentState .indexStatus != ClusterHealthStatus .RED ;
400- }
401-
402- /**
403- * Return true if the state moves from the index existing to the index not existing.
404- */
405- public static boolean isIndexDeleted (State previousState , State currentState ) {
406- return previousState .indexStatus != null && currentState .indexStatus == null ;
407- }
408-
409422 /**
410423 * State of the security index.
411424 */
0 commit comments