/* * 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.cassandra.config; import java.util.*; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.*; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.locator.*; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.thrift.CfDef; import org.apache.cassandra.thrift.KsDef; import org.apache.cassandra.tracing.Tracing; import static org.apache.cassandra.utils.FBUtilities.*; public final class KSMetaData { public final String name; public final Class<? extends AbstractReplicationStrategy> strategyClass; public final Map<String, String> strategyOptions; private final Map<String, CFMetaData> cfMetaData; public final boolean durableWrites; KSMetaData(String name, Class<? extends AbstractReplicationStrategy> strategyClass, Map<String, String> strategyOptions, boolean durableWrites, Iterable<CFMetaData> cfDefs) { this.name = name; this.strategyClass = strategyClass == null ? NetworkTopologyStrategy.class : strategyClass; this.strategyOptions = strategyOptions; Map<String, CFMetaData> cfmap = new HashMap<String, CFMetaData>(); for (CFMetaData cfm : cfDefs) cfmap.put(cfm.cfName, cfm); this.cfMetaData = Collections.unmodifiableMap(cfmap); this.durableWrites = durableWrites; } // For new user created keyspaces (through CQL) public static KSMetaData newKeyspace(String name, String strategyName, Map<String, String> options, boolean durableWrites) throws ConfigurationException { Class<? extends AbstractReplicationStrategy> cls = AbstractReplicationStrategy.getClass(strategyName); if (cls.equals(LocalStrategy.class)) throw new ConfigurationException("Unable to use given strategy class: LocalStrategy is reserved for internal use."); return newKeyspace(name, cls, options, durableWrites, Collections.<CFMetaData>emptyList()); } public static KSMetaData newKeyspace(String name, Class<? extends AbstractReplicationStrategy> strategyClass, Map<String, String> options, boolean durablesWrites, Iterable<CFMetaData> cfDefs) { return new KSMetaData(name, strategyClass, options, durablesWrites, cfDefs); } public static KSMetaData cloneWith(KSMetaData ksm, Iterable<CFMetaData> cfDefs) { return new KSMetaData(ksm.name, ksm.strategyClass, ksm.strategyOptions, ksm.durableWrites, cfDefs); } public static KSMetaData systemKeyspace() { List<CFMetaData> cfDefs = Arrays.asList(CFMetaData.BatchlogCf, CFMetaData.RangeXfersCf, CFMetaData.LocalCf, CFMetaData.PeersCf, CFMetaData.PeerEventsCf, CFMetaData.HintsCf, CFMetaData.IndexCf, CFMetaData.SchemaTriggersCf, CFMetaData.CounterIdCf, CFMetaData.SchemaKeyspacesCf, CFMetaData.SchemaColumnFamiliesCf, CFMetaData.SchemaColumnsCf, CFMetaData.CompactionLogCf, CFMetaData.CompactionHistoryCf, CFMetaData.PaxosCf, CFMetaData.SSTableActivityCF); return new KSMetaData(Keyspace.SYSTEM_KS, LocalStrategy.class, Collections.<String, String>emptyMap(), true, cfDefs); } public static KSMetaData traceKeyspace() { List<CFMetaData> cfDefs = Arrays.asList(CFMetaData.TraceSessionsCf, CFMetaData.TraceEventsCf); return new KSMetaData(Tracing.TRACE_KS, SimpleStrategy.class, ImmutableMap.of("replication_factor", "2"), true, cfDefs); } public static KSMetaData testMetadata(String name, Class<? extends AbstractReplicationStrategy> strategyClass, Map<String, String> strategyOptions, CFMetaData... cfDefs) { return new KSMetaData(name, strategyClass, strategyOptions, true, Arrays.asList(cfDefs)); } public static KSMetaData testMetadataNotDurable(String name, Class<? extends AbstractReplicationStrategy> strategyClass, Map<String, String> strategyOptions, CFMetaData... cfDefs) { return new KSMetaData(name, strategyClass, strategyOptions, false, Arrays.asList(cfDefs)); } public int hashCode() { return name.hashCode(); } public boolean equals(Object obj) { if (!(obj instanceof KSMetaData)) return false; KSMetaData other = (KSMetaData)obj; return other.name.equals(name) && ObjectUtils.equals(other.strategyClass, strategyClass) && ObjectUtils.equals(other.strategyOptions, strategyOptions) && other.cfMetaData.equals(cfMetaData) && other.durableWrites == durableWrites; } public Map<String, CFMetaData> cfMetaData() { return cfMetaData; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(name) .append(", rep strategy:") .append(strategyClass.getSimpleName()) .append("{") .append(StringUtils.join(cfMetaData.values(), ", ")) .append("}") .append(", strategy_options: ") .append(strategyOptions.toString()) .append(", durable_writes: ") .append(durableWrites); return sb.toString(); } public static String convertOldStrategyName(String name) { return name.replace("RackUnawareStrategy", "SimpleStrategy") .replace("RackAwareStrategy", "OldNetworkTopologyStrategy"); } public static Map<String,String> optsWithRF(final Integer rf) { Map<String, String> ret = new HashMap<String,String>(); ret.put("replication_factor", rf.toString()); return ret; } public static KSMetaData fromThrift(KsDef ksd, CFMetaData... cfDefs) throws ConfigurationException { Class<? extends AbstractReplicationStrategy> cls = AbstractReplicationStrategy.getClass(ksd.strategy_class); if (cls.equals(LocalStrategy.class)) throw new ConfigurationException("Unable to use given strategy class: LocalStrategy is reserved for internal use."); return new KSMetaData(ksd.name, cls, ksd.strategy_options == null ? Collections.<String, String>emptyMap() : ksd.strategy_options, ksd.durable_writes, Arrays.asList(cfDefs)); } public KsDef toThrift() { List<CfDef> cfDefs = new ArrayList<CfDef>(cfMetaData.size()); for (CFMetaData cfm : cfMetaData().values()) { // Don't expose CF that cannot be correctly handle by thrift; see CASSANDRA-4377 for further details if (cfm.isThriftCompatible()) cfDefs.add(cfm.toThrift()); } KsDef ksdef = new KsDef(name, strategyClass.getName(), cfDefs); ksdef.setStrategy_options(strategyOptions); ksdef.setDurable_writes(durableWrites); return ksdef; } public RowMutation toSchemaUpdate(KSMetaData newState, long modificationTimestamp) { return newState.toSchema(modificationTimestamp); } public KSMetaData validate() throws ConfigurationException { if (!CFMetaData.isNameValid(name)) throw new ConfigurationException(String.format("Keyspace name must not be empty, more than %s characters long, or contain non-alphanumeric-underscore characters (got \"%s\")", Schema.NAME_LENGTH, name)); // Attempt to instantiate the ARS, which will throw a ConfigException if the strategy_options aren't fully formed TokenMetadata tmd = StorageService.instance.getTokenMetadata(); IEndpointSnitch eps = DatabaseDescriptor.getEndpointSnitch(); AbstractReplicationStrategy.validateReplicationStrategy(name, strategyClass, tmd, eps, strategyOptions); for (CFMetaData cfm : cfMetaData.values()) cfm.validate(); return this; } public KSMetaData reloadAttributes() { Row ksDefRow = SystemKeyspace.readSchemaRow(name); if (ksDefRow.cf == null) throw new RuntimeException(String.format("%s not found in the schema definitions keyspaceName (%s).", name, SystemKeyspace.SCHEMA_KEYSPACES_CF)); return fromSchema(ksDefRow, Collections.<CFMetaData>emptyList()); } public RowMutation dropFromSchema(long timestamp) { RowMutation rm = new RowMutation(Keyspace.SYSTEM_KS, SystemKeyspace.getSchemaKSKey(name)); rm.delete(SystemKeyspace.SCHEMA_KEYSPACES_CF, timestamp); rm.delete(SystemKeyspace.SCHEMA_COLUMNFAMILIES_CF, timestamp); rm.delete(SystemKeyspace.SCHEMA_COLUMNS_CF, timestamp); rm.delete(SystemKeyspace.SCHEMA_TRIGGERS_CF, timestamp); return rm; } public RowMutation toSchema(long timestamp) { RowMutation rm = new RowMutation(Keyspace.SYSTEM_KS, SystemKeyspace.getSchemaKSKey(name)); ColumnFamily cf = rm.addOrGet(CFMetaData.SchemaKeyspacesCf); cf.addColumn(Column.create(durableWrites, timestamp, "durable_writes")); cf.addColumn(Column.create(strategyClass.getName(), timestamp, "strategy_class")); cf.addColumn(Column.create(json(strategyOptions), timestamp, "strategy_options")); for (CFMetaData cfm : cfMetaData.values()) cfm.toSchema(rm, timestamp); return rm; } /** * Deserialize only Keyspace attributes without nested ColumnFamilies * * @param row Keyspace attributes in serialized form * * @return deserialized keyspace without cf_defs */ public static KSMetaData fromSchema(Row row, Iterable<CFMetaData> cfms) { UntypedResultSet.Row result = QueryProcessor.resultify("SELECT * FROM system.schema_keyspaces", row).one(); try { return new KSMetaData(result.getString("keyspace_name"), AbstractReplicationStrategy.getClass(result.getString("strategy_class")), fromJsonMap(result.getString("strategy_options")), result.getBoolean("durable_writes"), cfms); } catch (ConfigurationException e) { throw new RuntimeException(e); } } /** * Deserialize Keyspace with nested ColumnFamilies * * @param serializedKs Keyspace in serialized form * @param serializedCFs Collection of the serialized ColumnFamilies * * @return deserialized keyspace with cf_defs */ public static KSMetaData fromSchema(Row serializedKs, Row serializedCFs) { Map<String, CFMetaData> cfs = deserializeColumnFamilies(serializedCFs); return fromSchema(serializedKs, cfs.values()); } /** * Deserialize ColumnFamilies from low-level schema representation, all of them belong to the same keyspace * * @param row * @return map containing name of the ColumnFamily and it's metadata for faster lookup */ public static Map<String, CFMetaData> deserializeColumnFamilies(Row row) { if (row.cf == null) return Collections.emptyMap(); Map<String, CFMetaData> cfms = new HashMap<String, CFMetaData>(); UntypedResultSet results = QueryProcessor.resultify("SELECT * FROM system.schema_columnfamilies", row); for (UntypedResultSet.Row result : results) { CFMetaData cfm = CFMetaData.fromSchema(result); cfms.put(cfm.cfName, cfm); } for (CFMetaData cfm : cfms.values()) { Row columnRow = SystemKeyspace.readSchemaRow(SystemKeyspace.SCHEMA_COLUMNS_CF, cfm.ksName, cfm.cfName); // This may replace some existing definition coming from the old key, column and // value aliases. But that's what we want (see CFMetaData.fromSchemaNoColumnsNoTriggers). for (ColumnDefinition cd : ColumnDefinition.fromSchema(columnRow, cfm)) cfm.addOrReplaceColumnDefinition(cd); cfm.rebuild(); } return cfms; } }