All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.xpack.core.ilm.GenerateSnapshotNameStep Maven / Gradle / Ivy

There is a newer version: 8.13.2
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */
package org.elasticsearch.xpack.core.ilm;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.index.Index;

import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.ILM_CUSTOM_METADATA_KEY;
import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.fromIndexMetadata;

/**
 * Generates a snapshot name for the given index and records it in the index metadata along with the provided snapshot repository.
 * 

* The generated snapshot name will be in the format {day-indexName-policyName-randomUUID} * eg.: 2020.03.30-myindex-mypolicy-cmuce-qfvmn_dstqw-ivmjc1etsa */ public class GenerateSnapshotNameStep extends ClusterStateActionStep { public static final String NAME = "generate-snapshot-name"; private static final Logger logger = LogManager.getLogger(CreateSnapshotStep.class); private static final IndexNameExpressionResolver.DateMathExpressionResolver DATE_MATH_RESOLVER = new IndexNameExpressionResolver.DateMathExpressionResolver(); private final String snapshotRepository; public GenerateSnapshotNameStep(StepKey key, StepKey nextStepKey, String snapshotRepository) { super(key, nextStepKey); this.snapshotRepository = snapshotRepository; } public String getSnapshotRepository() { return snapshotRepository; } @Override public ClusterState performAction(Index index, ClusterState clusterState) { IndexMetadata indexMetaData = clusterState.metadata().index(index); if (indexMetaData == null) { // Index must have been since deleted, ignore it logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().getAction(), index.getName()); return clusterState; } ClusterState.Builder newClusterStateBuilder = ClusterState.builder(clusterState); LifecycleExecutionState lifecycleState = fromIndexMetadata(indexMetaData); assert lifecycleState.getSnapshotName() == null : "index " + index.getName() + " should not have a snapshot generated by " + "the ilm policy but has " + lifecycleState.getSnapshotName(); LifecycleExecutionState.Builder newCustomData = LifecycleExecutionState.builder(lifecycleState); String policy = indexMetaData.getSettings().get(LifecycleSettings.LIFECYCLE_NAME); String snapshotNamePrefix = ("<{now/d}-" + index.getName() + "-" + policy + ">").toLowerCase(Locale.ROOT); String snapshotName = generateSnapshotName(snapshotNamePrefix); ActionRequestValidationException validationException = validateGeneratedSnapshotName(snapshotNamePrefix, snapshotName); if (validationException != null) { logger.warn("unable to generate a snapshot name as part of policy [{}] for index [{}] due to [{}]", policy, index.getName(), validationException.getMessage()); throw validationException; } newCustomData.setSnapshotName(snapshotName); newCustomData.setSnapshotRepository(snapshotRepository); newCustomData.setSnapshotIndexName(index.getName()); IndexMetadata.Builder indexMetadataBuilder = IndexMetadata.builder(indexMetaData); indexMetadataBuilder.putCustom(ILM_CUSTOM_METADATA_KEY, newCustomData.build().asMap()); newClusterStateBuilder.metadata(Metadata.builder(clusterState.getMetadata()).put(indexMetadataBuilder)); return newClusterStateBuilder.build(); } @Override public int hashCode() { return Objects.hash(super.hashCode(), snapshotRepository); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } GenerateSnapshotNameStep other = (GenerateSnapshotNameStep) obj; return super.equals(obj) && Objects.equals(snapshotRepository, other.snapshotRepository); } /** * Since snapshots need to be uniquely named, this method will resolve any date math used in * the provided name, as well as appending a unique identifier so expressions that may overlap * still result in unique snapshot names. */ public static String generateSnapshotName(String name) { return generateSnapshotName(name, new ResolverContext()); } public static String generateSnapshotName(String name, IndexNameExpressionResolver.Context context) { List candidates = DATE_MATH_RESOLVER.resolve(context, Collections.singletonList(name)); if (candidates.size() != 1) { throw new IllegalStateException("resolving snapshot name " + name + " generated more than one candidate: " + candidates); } // TODO: we are breaking the rules of UUIDs by lowercasing this here, find an alternative (snapshot names must be lowercase) return candidates.get(0) + "-" + UUIDs.randomBase64UUID().toLowerCase(Locale.ROOT); } /** * This is a context for the DateMathExpressionResolver, which does not require * {@code IndicesOptions} or {@code ClusterState} since it only uses the start * time to resolve expressions */ public static final class ResolverContext extends IndexNameExpressionResolver.Context { public ResolverContext() { this(System.currentTimeMillis()); } public ResolverContext(long startTime) { super(null, null, startTime, false, false, false, false, false); } @Override public ClusterState getState() { throw new UnsupportedOperationException("should never be called"); } @Override public IndicesOptions getOptions() { throw new UnsupportedOperationException("should never be called"); } } @Nullable public static ActionRequestValidationException validateGeneratedSnapshotName(String snapshotPrefix, String snapshotName) { ActionRequestValidationException err = new ActionRequestValidationException(); if (Strings.hasText(snapshotPrefix) == false) { err.addValidationError("invalid snapshot name [" + snapshotPrefix + "]: cannot be empty"); } if (snapshotName.contains("#")) { err.addValidationError("invalid snapshot name [" + snapshotPrefix + "]: must not contain '#'"); } if (snapshotName.charAt(0) == '_') { err.addValidationError("invalid snapshot name [" + snapshotPrefix + "]: must not start with '_'"); } if (snapshotName.toLowerCase(Locale.ROOT).equals(snapshotName) == false) { err.addValidationError("invalid snapshot name [" + snapshotPrefix + "]: must be lowercase"); } if (Strings.validFileName(snapshotName) == false) { err.addValidationError("invalid snapshot name [" + snapshotPrefix + "]: must not contain contain the following characters " + Strings.INVALID_FILENAME_CHARS); } if (err.validationErrors().size() > 0) { return err; } else { return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy