Removes NativeRealmMigrator since its no longer needed ()

With the new template and mapping update mechanisms introduced as part
of the Upgrade API work, the NativeRealmMigrator is no longer needed or
used.  This commit removes the NativeRealmMigrator code and the
associated tests for it.

Original commit: elastic/x-pack-elasticsearch@5d2d7a582c
This commit is contained in:
Ali Beyad 2017-07-19 12:19:48 -04:00 committed by GitHub
parent 7ed5df4068
commit fe9d99daed
9 changed files with 32 additions and 838 deletions
plugin/src

@ -318,7 +318,7 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin {
this.auditTrailService.set(auditTrailService);
final SecurityLifecycleService securityLifecycleService =
new SecurityLifecycleService(settings, clusterService, threadPool, client, licenseState, indexAuditTrail);
new SecurityLifecycleService(settings, clusterService, threadPool, client, indexAuditTrail);
final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, securityLifecycleService);
components.add(tokenService);

@ -19,10 +19,8 @@ import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail;
import org.elasticsearch.xpack.security.authc.esnative.NativeRealmMigrator;
import org.elasticsearch.xpack.security.support.IndexLifecycleManager;
import java.util.Arrays;
@ -60,21 +58,12 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust
public SecurityLifecycleService(Settings settings, ClusterService clusterService,
ThreadPool threadPool, InternalClient client,
XPackLicenseState licenseState,
@Nullable IndexAuditTrail indexAuditTrail) {
this(settings, clusterService, threadPool, client,
new NativeRealmMigrator(settings, licenseState, client), indexAuditTrail);
}
// package private for testing
SecurityLifecycleService(Settings settings, ClusterService clusterService, ThreadPool threadPool, InternalClient client,
NativeRealmMigrator migrator, @Nullable IndexAuditTrail indexAuditTrail) {
super(settings);
this.settings = settings;
this.threadPool = threadPool;
this.indexAuditTrail = indexAuditTrail;
this.securityIndex = new IndexLifecycleManager(settings, client, clusterService, threadPool, SECURITY_INDEX_NAME,
SECURITY_TEMPLATE_NAME, migrator);
this.securityIndex = new IndexLifecycleManager(settings, client, SECURITY_INDEX_NAME, SECURITY_TEMPLATE_NAME);
clusterService.addListener(this);
clusterService.addLifecycleListener(new LifecycleListener() {
@Override

@ -1,223 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.esnative;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.GroupedActionListener;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.client.SecurityClient;
import org.elasticsearch.xpack.security.support.IndexLifecycleManager;
import org.elasticsearch.xpack.security.user.BeatsSystemUser;
import org.elasticsearch.xpack.security.user.BuiltinUserInfo;
import org.elasticsearch.xpack.security.user.LogstashSystemUser;
import org.elasticsearch.xpack.security.user.User;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import static java.util.Collections.emptyList;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME;
import static org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.INDEX_TYPE;
import static org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.RESERVED_USER_TYPE;
/**
* Performs migration steps for the {@link NativeRealm} and {@link ReservedRealm}.
* When upgrading an Elasticsearch/X-Pack installation from a previous version, this class is responsible for ensuring that user/role
* data stored in the security index is converted to a format that is appropriate for the newly installed version.
*/
public class NativeRealmMigrator implements IndexLifecycleManager.IndexDataMigrator {
private final XPackLicenseState licenseState;
private final Logger logger;
private InternalClient client;
private final BuiltinUserInfo[] builtinUsers = new BuiltinUserInfo[] {
LogstashSystemUser.USER_INFO,
BeatsSystemUser.USER_INFO
};
public NativeRealmMigrator(Settings settings, XPackLicenseState licenseState, InternalClient internalClient) {
this.licenseState = licenseState;
this.logger = Loggers.getLogger(getClass(), settings);
this.client = internalClient;
}
/**
* Special care must be taken because this upgrade happens <strong>before</strong> the security-mapping is updated.
* We do it in that order because the version of the security-mapping controls the behaviour of the
* reserved and native realm
*
* @param listener A listener for the results of the upgrade. Calls {@link ActionListener#onFailure(Exception)} if a problem occurs,
* {@link ActionListener#onResponse(Object) onResponse(true)} if an upgrade is performed, or
* {@link ActionListener#onResponse(Object) onResponse(false)} if no upgrade was required.
* @see SecurityLifecycleService#securityIndexMappingAndTemplateSufficientToRead(ClusterState, Logger)
* @see SecurityLifecycleService#isSecurityIndexWriteable
* @see IndexLifecycleManager#mappingVersion
*/
@Override
public void performUpgrade(@Nullable Version previousVersion, ActionListener<Boolean> listener) {
try {
List<BiConsumer<Version, ActionListener<Void>>> tasks = collectUpgradeTasks(previousVersion);
if (tasks.isEmpty()) {
listener.onResponse(false);
} else {
final GroupedActionListener<Void> countDownListener = new GroupedActionListener<>(
ActionListener.wrap(r -> listener.onResponse(true), listener::onFailure), tasks.size(), emptyList()
);
logger.info("Performing {} security migration task(s) from version {}", tasks.size(), previousVersion);
tasks.forEach(t -> t.accept(previousVersion, countDownListener));
}
} catch (Exception e) {
listener.onFailure(e);
}
}
private List<BiConsumer<Version, ActionListener<Void>>> collectUpgradeTasks(@Nullable Version previousVersion) {
List<BiConsumer<Version, ActionListener<Void>>> tasks = new ArrayList<>();
for (BuiltinUserInfo info : builtinUsers) {
if (isNewUser(previousVersion, info)) {
tasks.add((v,l) -> createUserAsDisabled(info, v, l));
}
}
if (shouldConvertDefaultPasswords(previousVersion)) {
tasks.add(this::doConvertDefaultPasswords);
}
return tasks;
}
/**
* If we're upgrading from a security version where the new user did not exist, then we mark the user as disabled.
* Otherwise the user will exist with a default password, which is desirable for an <em>out-of-the-box</em> experience in fresh
* installs but problematic for already-locked-down upgrades.
*/
private boolean isNewUser(@Nullable Version previousVersion, BuiltinUserInfo info) {
return previousVersion != null
&& previousVersion.before(info.getDefinedSince())
&& previousVersion.onOrAfter(Version.V_5_0_0);
}
private void createUserAsDisabled(BuiltinUserInfo info, @Nullable Version previousVersion, ActionListener<Void> listener) {
logger.info("Upgrading security from version [{}] - new reserved user [{}] will default to disabled",
previousVersion, info.getName());
// Only clear the cache is authentication is allowed by the current license
// otherwise the license management checks will prevent it from completing successfully.
final boolean clearCache = licenseState.isAuthAllowed();
final String userName = info.getName();
client.prepareGet(SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(userName)).execute(
ActionListener.wrap(getResponse -> {
if (getResponse.isExists()) {
// the document exists - we shouldn't do anything
listener.onResponse(null);
} else {
client.prepareIndex(SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(userName))
.setSource(Requests.INDEX_CONTENT_TYPE, User.Fields.ENABLED.getPreferredName(), false,
User.Fields.PASSWORD.getPreferredName(), "",
User.Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE)
.setCreate(true)
.execute(ActionListener.wrap(r -> {
if (clearCache) {
new SecurityClient(client).prepareClearRealmCache()
.usernames(userName)
.execute(ActionListener.wrap(re -> listener.onResponse(null), listener::onFailure));
} else {
listener.onResponse(null);
}
}, listener::onFailure));
}
}, listener::onFailure));
}
/**
* Old versions of X-Pack security would assign the default password content to a user if it was enabled/disabled before the
* password was explicitly set to another value. If upgrading from one of those versions, then we want to change those users to be
* flagged as having a "default password" (which is stored as blank) so that {@link ReservedRealm#ACCEPT_DEFAULT_PASSWORD_SETTING}
* does the right thing.
*/
private boolean shouldConvertDefaultPasswords(@Nullable Version previousVersion) {
return previousVersion != null
&& previousVersion.before(Version.V_6_0_0_alpha1)
&& previousVersion.onOrAfter(Version.V_5_0_0);
}
@SuppressWarnings("unused")
private void doConvertDefaultPasswords(@Nullable Version previousVersion, ActionListener<Void> listener) {
client.prepareSearch(SECURITY_INDEX_NAME)
.setQuery(QueryBuilders.termQuery(User.Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE))
.setFetchSource(true)
.execute(ActionListener.wrap(searchResponse -> {
assert searchResponse.getHits().getTotalHits() <= 10 :
"there are more than 10 reserved users we need to change this to retrieve them all!";
Set<String> toConvert = new HashSet<>();
for (SearchHit searchHit : searchResponse.getHits()) {
Map<String, Object> sourceMap = searchHit.getSourceAsMap();
if (hasOldStyleDefaultPassword(sourceMap)) {
toConvert.add(searchHit.getId());
}
}
if (toConvert.isEmpty()) {
listener.onResponse(null);
} else {
GroupedActionListener<UpdateResponse> countDownListener = new GroupedActionListener<>(
ActionListener.wrap((r) -> listener.onResponse(null), listener::onFailure), toConvert.size(), emptyList()
);
toConvert.forEach(username -> {
logger.debug("Upgrading security from version [{}] - marking reserved user [{}] as having default password",
previousVersion, username);
client.prepareUpdate(SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(username))
.setDoc(User.Fields.PASSWORD.getPreferredName(), "",
User.Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE)
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
.execute(countDownListener);
});
}
}, listener::onFailure));
}
/**
* Determines whether the supplied source as a {@link Map} has its password explicitly set to be the default password
*/
public static boolean hasOldStyleDefaultPassword(Map<String, Object> userSource) {
// TODO we should store the hash as something other than a string... bytes?
final String passwordHash = (String) userSource.get(User.Fields.PASSWORD.getPreferredName());
if (passwordHash == null) {
throw new IllegalStateException("passwordHash should never be null");
} else if (passwordHash.isEmpty()) {
// we know empty is the new style
return false;
}
try (SecureString secureString = new SecureString(passwordHash.toCharArray())) {
return Hasher.BCRYPT.verify(ReservedRealm.EMPTY_PASSWORD_TEXT, secureString.getChars());
}
}
/**
* Gets the document's id field for the given user name.
*/
private static String getIdForUser(final String userName) {
return RESERVED_USER_TYPE + "-" + userName;
}
}

@ -5,15 +5,11 @@
*/
package org.elasticsearch.xpack.security.support;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@ -21,7 +17,6 @@ import java.util.stream.Collectors;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.ResourceAlreadyExistsException;
@ -30,10 +25,6 @@ import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.health.ClusterIndexHealth;
@ -42,23 +33,14 @@ import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.routing.IndexRoutingTable;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.template.TemplateUtils;
import org.elasticsearch.xpack.upgrade.IndexUpgradeCheck;
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING;
import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME;
/**
@ -73,20 +55,9 @@ public class IndexLifecycleManager extends AbstractComponent {
Pattern.quote("${security.template.version}");
public static int NEW_INDEX_VERSION = IndexUpgradeCheck.UPRADE_VERSION;
private static final int MAX_MIGRATE_ATTEMPTS = 10;
private final String indexName;
private final String templateName;
private final InternalClient client;
private final IndexDataMigrator migrator;
private final ClusterService clusterService;
private final ThreadPool threadPool;
private final AtomicBoolean templateCreationPending = new AtomicBoolean(false);
private final AtomicBoolean updateMappingPending = new AtomicBoolean(false);
private final AtomicReference<UpgradeState> migrateDataState = new AtomicReference<>(UpgradeState.NOT_STARTED);
private final AtomicInteger migrateDataAttempts = new AtomicInteger(0);
private final List<BiConsumer<ClusterIndexHealth, ClusterIndexHealth>> indexHealthChangeListeners = new CopyOnWriteArrayList<>();
@ -102,47 +73,17 @@ public class IndexLifecycleManager extends AbstractComponent {
NOT_STARTED, IN_PROGRESS, COMPLETE, FAILED
}
public interface IndexDataMigrator {
void performUpgrade(@Nullable Version previousVersion, ActionListener<Boolean> listener);
}
public static final IndexDataMigrator NULL_MIGRATOR = (version, listener) -> listener.onResponse(false);
public IndexLifecycleManager(Settings settings, InternalClient client, ClusterService clusterService, ThreadPool threadPool,
String indexName, String templateName, IndexDataMigrator migrator) {
public IndexLifecycleManager(Settings settings, InternalClient client, String indexName, String templateName) {
super(settings);
this.client = client;
this.indexName = indexName;
this.templateName = templateName;
this.migrator = migrator;
this.clusterService = clusterService;
this.threadPool = threadPool;
}
public boolean isTemplateUpToDate() {
return templateIsUpToDate;
}
public boolean isTemplateCreationPending() {
return templateCreationPending.get();
}
public boolean isMappingUpToDate() {
return mappingIsUpToDate;
}
public Version getMappingVersion() {
return mappingVersion;
}
public boolean checkMappingVersion(Predicate<Version> requiredVersion) {
return this.mappingVersion == null || requiredVersion.test(this.mappingVersion);
}
public boolean isMappingUpdatePending() {
return this.updateMappingPending.get();
}
public boolean indexExists() {
return indexExists;
}
@ -159,10 +100,6 @@ public class IndexLifecycleManager extends AbstractComponent {
return canWriteToIndex;
}
public UpgradeState getMigrationState() {
return this.migrateDataState.get();
}
/**
* Adds a listener which will be notified when the security index health changes. The previous and
* current health will be provided to the listener so that the listener can determine if any action
@ -323,179 +260,6 @@ public class IndexLifecycleManager extends AbstractComponent {
}
}
private void updateTemplate() {
// only put the template if this is not already in progress
if (templateCreationPending.compareAndSet(false, true)) {
putTemplate();
}
}
private boolean migrateData(ClusterState state, Runnable andThen) {
// only update the data if this is not already in progress
if (migrateDataState.compareAndSet(UpgradeState.NOT_STARTED, UpgradeState.IN_PROGRESS)) {
final Version previousVersion = oldestIndexMappingVersion(state);
migrator.performUpgrade(previousVersion, new ActionListener<Boolean>() {
@Override
public void onResponse(Boolean upgraded) {
migrateDataState.set(UpgradeState.COMPLETE);
andThen.run();
}
@Override
public void onFailure(Exception e) {
migrateDataState.set(UpgradeState.FAILED);
final int attempts = migrateDataAttempts.incrementAndGet();
logger.error(new ParameterizedMessage(
"failed to upgrade security [{}] data from version [{}] (Attempt {} of {})",
indexName, previousVersion, attempts, MAX_MIGRATE_ATTEMPTS),
e);
if (attempts < MAX_MIGRATE_ATTEMPTS) {
// The first retry is (1^5)ms = 1ms
// The last retry is (9^5)ms = 59s
final TimeValue retry = TimeValue.timeValueMillis((long) Math.pow(attempts, 5));
logger.info("Will attempt upgrade again in {}", retry);
threadPool.schedule(retry, ThreadPool.Names.SAME, IndexLifecycleManager.this::retryDataMigration);
} else {
logger.error("Security migration has failed after {} attempts. Restart the master node to try again.",
MAX_MIGRATE_ATTEMPTS);
}
}
@Override
public String toString() {
return getClass() + "{" + indexName + " migrator}";
}
});
return true;
} else {
if (migrateDataState.get() == UpgradeState.COMPLETE) {
andThen.run();
}
return false;
}
}
private void retryDataMigration() {
if (migrateDataState.compareAndSet(UpgradeState.FAILED, UpgradeState.NOT_STARTED)) {
processClusterState(clusterService.state());
}
}
private void updateMapping() {
// only update the mapping if this is not already in progress
if (updateMappingPending.compareAndSet(false, true)) {
putMappings();
}
}
private void putMappings() {
String template = TemplateUtils.loadTemplate("/" + templateName + ".json",
Version.CURRENT.toString(), TEMPLATE_VERSION_PATTERN);
Map<String, Object> typeMappingMap;
try {
typeMappingMap = convertToMap(JsonXContent.jsonXContent, template, false);
} catch (ElasticsearchParseException e) {
updateMappingPending.set(false);
logger.error(new ParameterizedMessage(
"failed to parse index template {}", templateName), e);
throw new ElasticsearchException("failed to parse index template {}", e, templateName);
}
// here go over all types found in the template and update them
// we need to wait for all types
final Map<String, PutMappingResponse> updateResults =
ConcurrentCollections.newConcurrentMap();
@SuppressWarnings("unchecked")
Map<String, Object> typeMappings = (Map<String, Object>) typeMappingMap.get("mappings");
int expectedResults = typeMappings.size();
for (String type : typeMappings.keySet()) {
// get the mappings from the template definition
@SuppressWarnings("unchecked")
Map<String, Object> typeMapping = (Map<String, Object>) typeMappings.get(type);
// update the mapping
putMapping(updateResults, expectedResults, type, typeMapping);
}
}
private void putMapping(final Map<String, PutMappingResponse> updateResults,
int expectedResults, final String type,
Map<String, Object> typeMapping) {
logger.debug("updating mapping of the [{}] index for type [{}]", indexName, type);
PutMappingRequest putMappingRequest = client.admin().indices()
.preparePutMapping(indexName).setSource(typeMapping).setType(type).request();
client.admin().indices().putMapping(putMappingRequest,
new ActionListener<PutMappingResponse>() {
@Override
public void onResponse(PutMappingResponse putMappingResponse) {
if (putMappingResponse.isAcknowledged() == false) {
updateMappingPending.set(false);
throw new ElasticsearchException("update mapping for type [{}]" +
" in index [{}] was not acknowledged", type, indexName);
} else {
updateResults.put(type, putMappingResponse);
if (updateResults.size() == expectedResults) {
updateMappingPending.set(false);
}
}
}
@Override
public void onFailure(Exception e) {
updateMappingPending.set(false);
logger.warn((Supplier<?>) () -> new ParameterizedMessage(
"failed to update mapping for type [{}] on index [{}]",
type, indexName), e);
}
@Override
public String toString() {
return getClass() + "{" + indexName + " PutMapping}";
}
});
}
private void putTemplate() {
logger.debug("putting the template [{}]", templateName);
String template = TemplateUtils.loadTemplate("/" + templateName + ".json",
Version.CURRENT.toString(), TEMPLATE_VERSION_PATTERN);
PutIndexTemplateRequest putTemplateRequest = client.admin().indices()
.preparePutTemplate(templateName)
.setSource(
new BytesArray(template.getBytes(StandardCharsets.UTF_8)),
XContentType.JSON)
.request();
client.admin().indices().putTemplate(putTemplateRequest,
new ActionListener<PutIndexTemplateResponse>() {
@Override
public void onResponse(PutIndexTemplateResponse putIndexTemplateResponse) {
templateCreationPending.set(false);
if (putIndexTemplateResponse.isAcknowledged()) {
templateIsUpToDate = true;
} else {
throw new ElasticsearchException(
"put template [{}] was not acknowledged", templateName
);
}
}
@Override
public void onFailure(Exception e) {
templateCreationPending.set(false);
logger.warn(new ParameterizedMessage(
"failed to put template [{}]", templateName), e);
}
@Override
public String toString() {
return getClass() + "{" + indexName + " PutTemplate}";
}
});
}
/**
* Creates the security index, if it does not already exist, then runs the given
* action on the security index.

@ -23,6 +23,7 @@ import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@ -39,7 +40,8 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportResponse;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.security.authc.esnative.NativeRealmMigrator;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.xpack.upgrade.actions.IndexUpgradeAction;
import org.elasticsearch.xpack.upgrade.actions.IndexUpgradeInfoAction;
@ -161,7 +163,7 @@ public class Upgrade implements ActionPlugin {
Set<String> toConvert = new HashSet<>();
for (SearchHit searchHit : searchResponse.getHits()) {
Map<String, Object> sourceMap = searchHit.getSourceAsMap();
if (NativeRealmMigrator.hasOldStyleDefaultPassword(sourceMap)) {
if (hasOldStyleDefaultPassword(sourceMap)) {
toConvert.add(searchHit.getId());
}
}
@ -199,6 +201,24 @@ public class Upgrade implements ActionPlugin {
}, listener::onFailure));
}
/**
* Determines whether the supplied source as a {@link Map} has its password explicitly set to be the default password
*/
private static boolean hasOldStyleDefaultPassword(Map<String, Object> userSource) {
// TODO we should store the hash as something other than a string... bytes?
final String passwordHash = (String) userSource.get(User.Fields.PASSWORD.getPreferredName());
if (passwordHash == null) {
throw new IllegalStateException("passwordHash should never be null");
} else if (passwordHash.isEmpty()) {
// we know empty is the new style
return false;
}
try (SecureString secureString = new SecureString(passwordHash.toCharArray())) {
return Hasher.BCRYPT.verify(ReservedRealm.EMPTY_PASSWORD_TEXT, secureString.getChars());
}
}
static BiFunction<InternalClient, ClusterService, IndexUpgradeCheck> getWatcherUpgradeCheckFactory(Settings settings) {
return (internalClient, clusterService) ->
new IndexUpgradeCheck<Boolean>("watcher",

@ -34,20 +34,16 @@ import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.MockTransportClient;
import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail;
import org.elasticsearch.xpack.security.authc.esnative.NativeRealmMigrator;
import org.elasticsearch.xpack.security.support.IndexLifecycleManager;
import org.elasticsearch.xpack.security.test.SecurityTestUtils;
import org.elasticsearch.xpack.template.TemplateUtils;
import org.junit.After;
import org.junit.Before;
import org.mockito.Mockito;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_TEMPLATE_NAME;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.securityIndexMappingAndTemplateUpToDate;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -83,16 +79,9 @@ public class SecurityLifecycleServiceTests extends ESTestCase {
}
}
NativeRealmMigrator nativeRealmMigrator = mock(NativeRealmMigrator.class);
Mockito.doAnswer(invocation -> {
ActionListener<Boolean> listener = (ActionListener) invocation.getArguments()[1];
listener.onResponse(false);
return null;
}).when(nativeRealmMigrator).performUpgrade(any(Version.class), any(ActionListener.class));
InternalClient client = new IClient(transportClient);
securityLifecycleService = new SecurityLifecycleService(Settings.EMPTY, clusterService,
threadPool, client, nativeRealmMigrator, mock(IndexAuditTrail.class));
threadPool, client, mock(IndexAuditTrail.class));
listeners = new CopyOnWriteArrayList<>();
}
@ -110,7 +99,6 @@ public class SecurityLifecycleServiceTests extends ESTestCase {
);
securityLifecycleService.clusterChanged(new ClusterChangedEvent("test-event",
clusterStateBuilder.build(), EMPTY_CLUSTER_STATE));
assertThat(securityLifecycleService.securityIndex().isTemplateUpToDate(), equalTo(true));
// No upgrade actions run
assertThat(listeners.size(), equalTo(0));
}
@ -133,7 +121,6 @@ public class SecurityLifecycleServiceTests extends ESTestCase {
ClusterState.Builder clusterStateBuilder = createClusterStateWithMappingAndTemplate(securityTemplateString);
securityLifecycleService.clusterChanged(new ClusterChangedEvent("test-event",
clusterStateBuilder.build(), EMPTY_CLUSTER_STATE));
assertTrue(securityLifecycleService.securityIndex().isMappingUpToDate());
assertThat(listeners.size(), equalTo(0));
}
@ -167,8 +154,6 @@ public class SecurityLifecycleServiceTests extends ESTestCase {
clusterStateBuilder.metaData(builder);
securityLifecycleService.clusterChanged(new ClusterChangedEvent("test-event", clusterStateBuilder.build()
, EMPTY_CLUSTER_STATE));
assertTrue(securityLifecycleService.securityIndex().isMappingUpToDate());
assertThat(securityLifecycleService.securityIndex().getMappingVersion(), nullValue());
assertThat(listeners.size(), equalTo(0));
}

@ -1,256 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.esnative;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.get.GetAction;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexAction;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchAction;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateAction;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheAction;
import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheRequest;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.user.BeatsSystemUser;
import org.elasticsearch.xpack.security.user.ElasticUser;
import org.elasticsearch.xpack.security.user.KibanaUser;
import org.elasticsearch.xpack.security.user.LogstashSystemUser;
import org.elasticsearch.xpack.security.user.User;
import org.junit.Before;
import org.mockito.ArgumentCaptor;
import static java.util.Collections.emptyMap;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
public class NativeRealmMigratorTests extends ESTestCase {
private Map<String, Map<String, Object>> reservedUsers;
private InternalClient internalClient;
private Client mockClient;
private NativeRealmMigrator migrator;
private XPackLicenseState licenseState;
@Before
public void setupMocks() throws IOException {
final boolean allowClearCache = randomBoolean();
mockClient = mock(Client.class);
ThreadPool threadPool = mock(ThreadPool.class);
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
internalClient = new InternalClient(Settings.EMPTY, threadPool, mockClient);
doAnswer(invocationOnMock -> {
SearchRequest request = (SearchRequest) invocationOnMock.getArguments()[1];
ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2];
if (request.indices().length == 1 && request.indices()[0].equals(SecurityLifecycleService.SECURITY_INDEX_NAME)) {
SearchResponse response = new SearchResponse() {
@Override
public SearchHits getHits() {
List<SearchHit> hits = reservedUsers.entrySet().stream()
.map((info) -> {
SearchHit hit = new SearchHit(randomInt(), info.getKey(), null, null, emptyMap());
try {
hit.sourceRef(JsonXContent.contentBuilder().map(info.getValue()).bytes());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return hit;
}).collect(Collectors.toList());
return new SearchHits(hits.toArray(new SearchHit[0]), (long) reservedUsers.size(), 0.0f);
}
};
listener.onResponse(response);
} else {
listener.onResponse(null);
}
return Void.TYPE;
}).when(mockClient).execute(eq(SearchAction.INSTANCE), any(SearchRequest.class), any(ActionListener.class));
doAnswer(invocationOnMock -> {
GetRequest request = (GetRequest) invocationOnMock.getArguments()[1];
ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2];
if (request.indices().length == 1 && request.indices()[0].equals(SecurityLifecycleService.SECURITY_INDEX_NAME)
&& request.type().equals(NativeUsersStore.INDEX_TYPE)
&& request.id().startsWith(NativeUsersStore.RESERVED_USER_TYPE)) {
final boolean exists = reservedUsers.get(request.id()) != null;
GetResult getResult = new GetResult(SecurityLifecycleService.SECURITY_INDEX_NAME, NativeUsersStore.INDEX_TYPE,
NativeUsersStore.getIdForUser(NativeUsersStore.RESERVED_USER_TYPE, request.id()),
randomLong(), exists, JsonXContent.contentBuilder().map(reservedUsers.get(request.id())).bytes(), emptyMap());
listener.onResponse(new GetResponse(getResult));
} else {
listener.onResponse(null);
}
return Void.TYPE;
}).when(mockClient).execute(eq(GetAction.INSTANCE), any(GetRequest.class), any(ActionListener.class));
doAnswer(invocationOnMock -> {
ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2];
listener.onResponse(null);
return Void.TYPE;
}).when(mockClient).execute(eq(ClearRealmCacheAction.INSTANCE), any(ClearRealmCacheRequest.class), any(ActionListener.class));
doAnswer(invocationOnMock -> {
ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2];
listener.onResponse(null);
return Void.TYPE;
}).when(mockClient).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class));
doAnswer(invocationOnMock -> {
ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2];
listener.onResponse(null);
return Void.TYPE;
}).when(mockClient).execute(eq(UpdateAction.INSTANCE), any(UpdateRequest.class), any(ActionListener.class));
final Settings settings = Settings.EMPTY;
licenseState = mock(XPackLicenseState.class);
when(licenseState.isAuthAllowed()).thenReturn(allowClearCache);
migrator = new NativeRealmMigrator(settings, licenseState, internalClient);
}
public void testNoChangeOnFreshInstall() throws Exception {
verifyUpgrade(null, null, false);
}
public void testNoChangeOnUpgradeAfterV5_3() throws Exception {
verifyUpgrade(randomFrom(Version.V_6_0_0_alpha1), null, false);
}
public void testNoChangeOnUpgradeFromV5alpha1() throws Exception {
verifyUpgrade(randomFrom(Version.V_5_0_0_alpha1), null, false);
}
public void testDisableLogstashBeatsAndConvertPasswordsOnUpgradeFromVersionPriorToV5_2() throws Exception {
this.reservedUsers = Collections.singletonMap(
KibanaUser.NAME,
MapBuilder.<String, Object>newMapBuilder()
.put(User.Fields.PASSWORD.getPreferredName(), new String(Hasher.BCRYPT.hash(ReservedRealm.EMPTY_PASSWORD_TEXT)))
.put(User.Fields.ENABLED.getPreferredName(), false)
.immutableMap()
);
final String[] disabledUsers = Arrays.asList(LogstashSystemUser.NAME, BeatsSystemUser.NAME)
.stream().map(s -> NativeUsersStore.RESERVED_USER_TYPE + "-" + s).toArray(String[]::new);
verifyUpgrade(randomFrom(Version.V_5_1_1, Version.V_5_0_2, Version.V_5_0_0), disabledUsers, true);
}
public void testDisableBeatsAndConvertPasswordsOnUpgradeFromVersionPriorToV6() throws Exception {
this.reservedUsers = Collections.singletonMap(
KibanaUser.NAME,
MapBuilder.<String, Object>newMapBuilder()
.put(User.Fields.PASSWORD.getPreferredName(), new String(Hasher.BCRYPT.hash(ReservedRealm.EMPTY_PASSWORD_TEXT)))
.put(User.Fields.ENABLED.getPreferredName(), false)
.immutableMap()
);
String[] disabledUsers = new String[]{ NativeUsersStore.RESERVED_USER_TYPE + "-" + BeatsSystemUser.NAME };
Version version = randomFrom(Version.V_5_3_0, Version.V_5_2_1);
verifyUpgrade(version, disabledUsers, true);
}
public void testConvertPasswordsOnUpgradeFromVersion5_2() throws Exception {
this.reservedUsers = randomSubsetOf(randomIntBetween(0, 3), LogstashSystemUser.NAME, KibanaUser.NAME, ElasticUser.NAME)
.stream().collect(Collectors.toMap(Function.identity(),
name -> MapBuilder.<String, Object>newMapBuilder()
.put(User.Fields.PASSWORD.getPreferredName(),
new String(Hasher.BCRYPT.hash(ReservedRealm.EMPTY_PASSWORD_TEXT)))
.put(User.Fields.ENABLED.getPreferredName(), randomBoolean())
.immutableMap()
));
String[] disabledUsers = new String[]{ NativeUsersStore.RESERVED_USER_TYPE + "-" + BeatsSystemUser.NAME };
verifyUpgrade(Version.V_5_2_0, disabledUsers, true);
}
private void verifyUpgrade(Version fromVersion, String[] disabledUsers, boolean convertDefaultPasswords) throws Exception {
final PlainActionFuture<Boolean> future = doUpgrade(fromVersion);
boolean expectedResult = false;
if (disabledUsers != null) {
final int userCount = disabledUsers.length;
final boolean clearCache = licenseState.isAuthAllowed();
ArgumentCaptor<GetRequest> captor = ArgumentCaptor.forClass(GetRequest.class);
verify(mockClient, times(userCount)).execute(eq(GetAction.INSTANCE), captor.capture(), any(ActionListener.class));
for (int i = 0; i < userCount; i++) {
assertEquals(disabledUsers[i], captor.getAllValues().get(i).id());
}
ArgumentCaptor<IndexRequest> indexCaptor = ArgumentCaptor.forClass(IndexRequest.class);
verify(mockClient, times(userCount)).execute(eq(IndexAction.INSTANCE), indexCaptor.capture(), any(ActionListener.class));
for (int i = 0; i < userCount; i++) {
assertEquals(disabledUsers[i], captor.getAllValues().get(i).id());
assertEquals(false, indexCaptor.getValue().sourceAsMap().get(User.Fields.ENABLED.getPreferredName()));
}
if (clearCache) {
verify(mockClient, times(userCount)).execute(eq(ClearRealmCacheAction.INSTANCE), any(ClearRealmCacheRequest.class),
any(ActionListener.class));
}
expectedResult = true;
}
if (convertDefaultPasswords) {
verify(mockClient).execute(eq(SearchAction.INSTANCE), any(SearchRequest.class), any(ActionListener.class));
ArgumentCaptor<UpdateRequest> captor = ArgumentCaptor.forClass(UpdateRequest.class);
verify(mockClient, times(this.reservedUsers.size()))
.execute(eq(UpdateAction.INSTANCE), captor.capture(), any(ActionListener.class));
final List<UpdateRequest> requests = captor.getAllValues();
this.reservedUsers.keySet().forEach(u -> {
UpdateRequest request = requests.stream()
.filter(r -> r.id().equals(NativeUsersStore.getIdForUser(NativeUsersStore.RESERVED_USER_TYPE, u)))
.findFirst().get();
assertThat(request.validate(), nullValue(ActionRequestValidationException.class));
assertThat(request.doc().sourceAsMap(), hasEntry(is(User.Fields.PASSWORD.getPreferredName()), is("")));
assertThat(request.getRefreshPolicy(), equalTo(WriteRequest.RefreshPolicy.IMMEDIATE));
});
expectedResult = true;
}
verifyNoMoreInteractions(mockClient);
assertThat(future.get(), is(expectedResult));
}
private PlainActionFuture<Boolean> doUpgrade(Version fromVersion) {
final PlainActionFuture<Boolean> future = new PlainActionFuture<>();
migrator.performUpgrade(fromVersion, future);
return future;
}
}

@ -187,7 +187,7 @@ public class NativeRolesStoreTests extends ESTestCase {
final AtomicBoolean methodCalled = new AtomicBoolean(false);
final SecurityLifecycleService securityLifecycleService =
new SecurityLifecycleService(Settings.EMPTY, clusterService, threadPool, internalClient,
licenseState, mock(IndexAuditTrail.class));
mock(IndexAuditTrail.class));
final NativeRolesStore rolesStore = new NativeRolesStore(Settings.EMPTY, internalClient, licenseState, securityLifecycleService) {
@Override
void innerPutRole(final PutRoleRequest request, final RoleDescriptor role, final ActionListener<Boolean> listener) {

@ -20,9 +20,6 @@ import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingAction;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterChangedEvent;
@ -39,7 +36,6 @@ import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
import org.elasticsearch.cluster.routing.RoutingTable;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.cluster.routing.UnassignedInfo;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
@ -55,9 +51,7 @@ import org.hamcrest.Matchers;
import org.junit.Before;
import static org.elasticsearch.cluster.routing.RecoverySource.StoreRecoverySource.EXISTING_STORE_INSTANCE;
import static org.elasticsearch.xpack.security.support.IndexLifecycleManager.NULL_MIGRATOR;
import static org.elasticsearch.xpack.security.support.IndexLifecycleManager.TEMPLATE_VERSION_PATTERN;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -68,19 +62,14 @@ public class IndexLifecycleManagerTests extends ESTestCase {
public static final String INDEX_NAME = "IndexLifecycleManagerTests";
private static final String TEMPLATE_NAME = "IndexLifecycleManagerTests-template";
private IndexLifecycleManager manager;
private IndexLifecycleManager.IndexDataMigrator migrator;
private Map<Action<?, ?, ?>, Map<ActionRequest, ActionListener<?>>> actions;
private ThreadPool threadPool;
private ClusterService clusterService;
@Before
public void setUpManager() {
final Client mockClient = mock(Client.class);
threadPool = mock(ThreadPool.class);
final ThreadPool threadPool = mock(ThreadPool.class);
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
clusterService = mock(ClusterService.class);
actions = new LinkedHashMap<>();
final InternalClient client = new InternalClient(Settings.EMPTY, threadPool, mockClient) {
@Override
@ -94,11 +83,7 @@ public class IndexLifecycleManagerTests extends ESTestCase {
actions.put(action, map);
}
};
migrator = NULL_MIGRATOR;
manager = new IndexLifecycleManager(Settings.EMPTY, client, clusterService, threadPool, INDEX_NAME, TEMPLATE_NAME,
// Wrap the migrator in a lambda so that individual tests can override the migrator implementation.
(previousVersion, listener) -> migrator.performUpgrade(previousVersion, listener)
);
manager = new IndexLifecycleManager(Settings.EMPTY, client, INDEX_NAME, TEMPLATE_NAME);
}
public void testIndexWithUpToDateMappingAndTemplate() throws IOException {
@ -108,7 +93,9 @@ public class IndexLifecycleManagerTests extends ESTestCase {
markShardsAvailable(clusterStateBuilder);
manager.clusterChanged(event(clusterStateBuilder));
assertCompleteState(false);
assertThat(manager.indexExists(), Matchers.equalTo(true));
assertThat(manager.isAvailable(), Matchers.equalTo(true));
assertThat(manager.isWritable(), Matchers.equalTo(true));
}
public void testIndexWithoutPrimaryShards() throws IOException {
@ -219,85 +206,13 @@ public class IndexLifecycleManagerTests extends ESTestCase {
private void assertInitialState() {
assertThat(manager.indexExists(), Matchers.equalTo(false));
assertThat(manager.isAvailable(), Matchers.equalTo(false));
assertThat(manager.isTemplateUpToDate(), Matchers.equalTo(false));
assertThat(manager.isTemplateCreationPending(), Matchers.equalTo(false));
assertThat(manager.isMappingUpToDate(), Matchers.equalTo(false));
assertThat(manager.getMappingVersion(), Matchers.nullValue());
assertThat(manager.isMappingUpdatePending(), Matchers.equalTo(false));
assertThat(manager.getMigrationState(), Matchers.equalTo(IndexLifecycleManager.UpgradeState.NOT_STARTED));
assertThat(manager.isWritable(), Matchers.equalTo(false));
}
private void assertIndexUpToDateButNotAvailable() {
assertThat(manager.indexExists(), Matchers.equalTo(true));
assertThat(manager.isAvailable(), Matchers.equalTo(false));
assertThat(manager.isTemplateUpToDate(), Matchers.equalTo(true));
assertThat(manager.isTemplateCreationPending(), Matchers.equalTo(false));
assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true));
assertThat(manager.getMappingVersion(), Matchers.equalTo(Version.CURRENT));
assertThat(manager.isMappingUpdatePending(), Matchers.equalTo(false));
assertThat(manager.isWritable(), Matchers.equalTo(true));
assertThat(manager.getMigrationState(), Matchers.equalTo(IndexLifecycleManager.UpgradeState.NOT_STARTED));
}
private void assertTemplateAndMappingOutOfDate(boolean templateUpdatePending, boolean mappingUpdatePending,
IndexLifecycleManager.UpgradeState migrationState) {
assertThat(manager.indexExists(), Matchers.equalTo(true));
assertThat(manager.isAvailable(), Matchers.equalTo(true));
assertThat(manager.isTemplateUpToDate(), Matchers.equalTo(!templateUpdatePending));
assertThat(manager.isTemplateCreationPending(), Matchers.equalTo(templateUpdatePending));
assertThat(manager.isMappingUpToDate(), Matchers.equalTo(false));
assertThat(manager.getMappingVersion(), Matchers.equalTo(Version.V_5_1_2));
assertThat(manager.isMappingUpdatePending(), Matchers.equalTo(mappingUpdatePending));
assertThat(manager.isWritable(), Matchers.equalTo(false));
assertThat(manager.getMigrationState(), Matchers.equalTo(migrationState));
if (templateUpdatePending) {
final Map<ActionRequest, ActionListener<?>> requests = actions.get(PutIndexTemplateAction.INSTANCE);
assertThat(requests, notNullValue());
assertThat(requests.size(), Matchers.equalTo(1));
final ActionRequest request = requests.keySet().iterator().next();
assertThat(request, Matchers.instanceOf(PutIndexTemplateRequest.class));
assertThat(((PutIndexTemplateRequest) request).name(), Matchers.equalTo(TEMPLATE_NAME));
}
if (mappingUpdatePending) {
final Map<ActionRequest, ActionListener<?>> requests = actions.get(PutMappingAction.INSTANCE);
assertThat(requests, notNullValue());
assertThat(requests.size(), Matchers.equalTo(1));
final ActionRequest request = requests.keySet().iterator().next();
assertThat(request, Matchers.instanceOf(PutMappingRequest.class));
assertThat(((PutMappingRequest) request).indices(), Matchers.arrayContainingInAnyOrder(INDEX_NAME));
assertThat(((PutMappingRequest) request).type(), Matchers.equalTo("doc"));
}
}
private void assertCompleteState(boolean expectMigration) {
assertThat(manager.indexExists(), Matchers.equalTo(true));
assertThat(manager.isAvailable(), Matchers.equalTo(true));
assertThat(manager.isTemplateUpToDate(), Matchers.equalTo(true));
assertThat(manager.isTemplateCreationPending(), Matchers.equalTo(false));
assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true));
assertThat(manager.getMappingVersion(), Matchers.equalTo(Version.CURRENT));
assertThat(manager.isMappingUpdatePending(), Matchers.equalTo(false));
assertThat(manager.isWritable(), Matchers.equalTo(true));
if (expectMigration) {
assertThat(manager.getMigrationState(), Matchers.equalTo(IndexLifecycleManager.UpgradeState.COMPLETE));
} else {
assertThat(manager.getMigrationState(), Matchers.equalTo(IndexLifecycleManager.UpgradeState.NOT_STARTED));
}
}
public static ClusterState.Builder createClusterState(String indexName, String templateName) throws IOException {