blob: 4174e116a207ad05bf58b6af81a5729966c1a53c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.
*
*/
package org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import org.apache.skywalking.library.elasticsearch.response.Mappings;
import org.apache.skywalking.oap.server.core.Const;
import org.apache.skywalking.oap.server.library.client.elasticsearch.ElasticSearchClient;
import org.apache.skywalking.oap.server.library.util.CollectionUtils;
public class IndexStructures {
private final Map<String, Fields> mappingStructures;
private final Map<String, UpdatableIndexSettings> indexSettingStructures;
public IndexStructures() {
this.mappingStructures = new HashMap<>();
this.indexSettingStructures = new HashMap<>();
}
public Mappings getMapping(String tableName) {
Map<String, Object> properties =
mappingStructures.containsKey(tableName) ?
mappingStructures.get(tableName).properties : new HashMap<>();
Mappings.Source source =
mappingStructures.containsKey(tableName) ?
mappingStructures.get(tableName).source : new Mappings.Source();
return Mappings.builder()
.type(ElasticSearchClient.TYPE)
.properties(properties)
.source(source)
.build();
}
/**
* Add or append mapping/settings when the current structures don't contain the input structure or having
* new fields in it.
*/
public void putStructure(String tableName, Mappings mapping, Map<String, Object> settings) {
if (CollectionUtils.isNotEmpty(settings) && Objects.nonNull(settings.get("index"))) {
this.indexSettingStructures.putIfAbsent(tableName, new UpdatableIndexSettings(
(Map<String, Object>) settings.get("index")));
}
if (Objects.isNull(mapping)
|| Objects.isNull(mapping.getProperties())
|| mapping.getProperties().isEmpty()) {
return;
}
Fields fields = new Fields(mapping);
if (mappingStructures.containsKey(tableName)) {
mappingStructures.get(tableName).appendNewFields(fields);
} else {
mappingStructures.put(tableName, fields);
}
}
/**
* Returns mappings with fields that not exist in the input mappings.
* The input mappings should be history mapping from current index.
* Do not return _source config to avoid current index update conflict.
*/
public Mappings diffMappings(String tableName, Mappings mappings) {
if (!mappingStructures.containsKey(tableName)) {
return new Mappings();
}
Map<String, Object> diffProperties =
mappingStructures.get(tableName).diffFields(new Fields(mappings));
return Mappings.builder()
.type(ElasticSearchClient.TYPE)
.properties(diffProperties)
.build();
}
/**
* Returns true when the current structures already contains the properties of the input
* mappings.
*/
public boolean containsMapping(String tableName, Mappings mappings) {
if (Objects.isNull(mappings) ||
CollectionUtils.isEmpty(mappings.getProperties())) {
return true;
}
return mappingStructures.containsKey(tableName)
&& mappingStructures.get(tableName)
.containsAllFields(new Fields(mappings));
}
/**
* Check whether all the fields are already defined regardless of the field types.
* When OAP runs in no-init mode, it doesn't care about the field types, it just
* cares about whether the data can be ingested without reporting error.
* OAP running in init mode should take care of the field types.
*/
public boolean containsFieldNames(String tableName, Mappings mappings) {
if (Objects.isNull(mappings) ||
CollectionUtils.isEmpty(mappings.getProperties())) {
return true;
}
return mappingStructures.containsKey(tableName) &&
mappingStructures.get(tableName)
.containsAllFieldNames(new Fields(mappings));
}
/**
* Returns true when the current index setting equals the input.
*/
public boolean compareIndexSetting(String tableName, Map<String, Object> settings) {
if ((CollectionUtils.isEmpty(settings) || CollectionUtils.isEmpty((Map) settings.get("index")))
&& Objects.isNull(indexSettingStructures.get(tableName))) {
return true;
}
return indexSettingStructures.containsKey(tableName)
&& indexSettingStructures.get(tableName).
equals(new UpdatableIndexSettings((Map<String, Object>) settings.get("index")));
}
/**
* The mapping properties of the template or index.
*/
public static class Fields {
private final Map<String, Object> properties;
private final Mappings.Source source;
private Fields(Mappings mapping) {
this.properties = mapping.getProperties();
this.source = mapping.getSource();
}
/**
* Returns ture when the input fields have already been stored in the properties.
*/
private boolean containsAllFields(Fields fields) {
if (this.properties.size() < fields.properties.size()) {
return false;
}
boolean isContains = fields.properties.entrySet().stream()
.allMatch(item -> Objects.equals(properties.get(item.getKey()), item.getValue()));
if (!isContains) {
return false;
}
Set<String> inputExcludes = fields.source.getExcludes();
Set<String> excludes = source.getExcludes();
//need to add new excludes
if (!excludes.containsAll(inputExcludes)) {
return false;
}
//need to delete existing excludes
for (String p : fields.properties.keySet()) {
if (!inputExcludes.contains(p) && excludes.contains(p)) {
return false;
}
}
return true;
}
private boolean containsAllFieldNames(Fields fields) {
if (this.properties.size() < fields.properties.size()) {
return false;
}
return this.properties.keySet().containsAll(fields.properties.keySet());
}
/**
* Append new fields and update.
* Properties combine input and exist, update property's attribute, won't remove old one.
* If the existed `excludes` contains a not existing property, the excluded field would be removed.
*/
private void appendNewFields(Fields fields) {
properties.putAll(fields.properties);
Set<String> inputExcludes = fields.source.getExcludes();
Set<String> excludes = source.getExcludes();
excludes.addAll(inputExcludes);
fields.properties.keySet().forEach(p -> {
if (!inputExcludes.contains(p) && excludes.contains(p)) {
excludes.remove(p);
}
});
}
/**
* Returns the properties that not exist in the input fields.
*/
private Map<String, Object> diffFields(Fields fields) {
return this.properties.entrySet().stream()
.filter(e -> !fields.properties.containsKey(e.getKey()))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
}
}
/**
* The index settings structure which only includes needs to compare for update fields
*/
@EqualsAndHashCode
public static class UpdatableIndexSettings {
private final String replicas;
private final String shards;
public UpdatableIndexSettings(Map<String, Object> indexSettings) {
this.replicas = String.valueOf(indexSettings.getOrDefault("number_of_replicas", Const.EMPTY_STRING));
this.shards = String.valueOf(indexSettings.getOrDefault("number_of_shards", Const.EMPTY_STRING));
}
}
}