/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate licenses * this file to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.blob.v2; import com.google.common.annotations.VisibleForTesting; import io.crate.plugin.IndexEventListenerProxy; import org.elasticsearch.cluster.routing.ShardIterator; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.shard.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static io.crate.blob.v2.BlobIndex.isBlobIndex; /** * Manages the creation and deletion of BlobIndex and BlobShard instances. */ public class BlobIndicesService extends AbstractComponent implements IndexEventListener { public static final Setting<Boolean> SETTING_INDEX_BLOBS_ENABLED = Setting.boolSetting( "index.blobs.enabled", false, Setting.Property.IndexScope); public static final Setting<String> SETTING_INDEX_BLOBS_PATH = Setting.simpleString( "index.blobs.path", Setting.Property.IndexScope); public static final Setting<String> SETTING_BLOBS_PATH = Setting.simpleString( "blobs.path", Setting.Property.NodeScope); private final ClusterService clusterService; @VisibleForTesting final Map<String, BlobIndex> indices = new ConcurrentHashMap<>(); @Nullable private final Path globalBlobPath; @Inject public BlobIndicesService(Settings settings, ClusterService clusterService, IndexEventListenerProxy indexEventListenerProxy) { super(settings); this.clusterService = clusterService; globalBlobPath = getGlobalBlobPath(settings); indexEventListenerProxy.addFirst(this); } @Nullable public static Path getGlobalBlobPath(Settings settings) { String customGlobalBlobPathSetting = SETTING_BLOBS_PATH.get(settings); if (Strings.isNullOrEmpty(customGlobalBlobPathSetting)) { return null; } Path globalBlobPath = PathUtils.get(customGlobalBlobPathSetting); ensureExistsAndWritable(globalBlobPath); return globalBlobPath; } @Override public void afterIndexCreated(IndexService indexService) { String indexName = indexService.index().getName(); if (isBlobIndex(indexName)) { BlobIndex oldBlobIndex = indices.put(indexName, new BlobIndex(logger, globalBlobPath)); assert oldBlobIndex == null : "There must not be an index present if a new index is created"; } } @Override public void afterIndexClosed(Index index, Settings indexSettings) { String indexName = index.getName(); if (isBlobIndex(indexName)) { BlobIndex blobIndex = indices.remove(indexName); assert blobIndex != null : "BlobIndex not found on afterIndexDeleted"; /* * Calling delete within IndexClosed is okay because Crate doesn't support closing indices. * * Can't do this in the `afterIndexDeleted` event because the master-node creates test indices for which * no `afterIndexDeleted´ event is created: These would then leak. */ blobIndex.delete(); } } @Override public void afterIndexShardCreated(IndexShard indexShard) { String index = indexShard.shardId().getIndexName(); if (isBlobIndex(index)) { BlobIndex blobIndex = indices.get(index); assert blobIndex != null : "blobIndex must exists if a shard is created in it"; blobIndex.createShard(indexShard); } } @Override public void indexShardStateChanged(IndexShard indexShard, @Nullable IndexShardState previousState, IndexShardState currentState, @Nullable String reason) { if (currentState == IndexShardState.POST_RECOVERY) { String index = indexShard.shardId().getIndexName(); if (isBlobIndex(index)) { BlobIndex blobIndex = indices.get(index); blobIndex.initializeShard(indexShard); } } } @Override public void afterIndexShardDeleted(ShardId shardId, Settings indexSettings) { String index = shardId.getIndexName(); if (isBlobIndex(index)) { BlobIndex blobIndex = indices.get(index); if (blobIndex != null) { blobIndex.removeShard(shardId); } } } @Nullable public BlobShard blobShard(ShardId shardId) { BlobIndex blobIndex = indices.get(shardId.getIndexName()); if (blobIndex == null) { return null; } return blobIndex.getShard(shardId.id()); } public BlobShard blobShardSafe(ShardId shardId) { String index = shardId.getIndexName(); if (isBlobIndex(index)) { BlobShard blobShard = blobShard(shardId); if (blobShard == null) { throw new ShardNotFoundException(shardId); } return blobShard; } throw new BlobsDisabledException(shardId.getIndex()); } public BlobShard localBlobShard(String index, String digest) { return blobShardSafe(localShardId(index, digest)); } private ShardId localShardId(String index, String digest) { ShardIterator si = clusterService.operationRouting().getShards( clusterService.state(), index, null, digest, "_only_local"); return si.shardId(); } static boolean ensureExistsAndWritable(Path blobsPath) { if (Files.exists(blobsPath)) { if (!Files.isDirectory(blobsPath)) { throw new SettingsException( String.format(Locale.ENGLISH, "blobs path '%s' is a file, must be a directory", blobsPath)); } if (!Files.isWritable(blobsPath)) { throw new SettingsException( String.format(Locale.ENGLISH, "blobs path '%s' is not writable", blobsPath)); } } else { try { Files.createDirectories(blobsPath); } catch (IOException e) { throw new SettingsException( String.format(Locale.ENGLISH, "blobs path '%s' could not be created", blobsPath)); } } return true; } }