diff --git a/bin/schematool b/bin/schematool new file mode 100755 index 0000000000..3e2d0e71c6 --- /dev/null +++ b/bin/schematool @@ -0,0 +1,49 @@ +#!/bin/sh + +# 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. + +if [ "x$CASSANDRA_INCLUDE" = "x" ]; then + for include in /usr/share/cassandra/cassandra.in.sh \ + /usr/local/share/cassandra/cassandra.in.sh \ + /opt/cassandra/cassandra.in.sh \ + ~/.cassandra.in.sh \ + `dirname $0`/cassandra.in.sh; do + if [ -r $include ]; then + . $include + break + fi + done +elif [ -r $CASSANDRA_INCLUDE ]; then + . $CASSANDRA_INCLUDE +fi + +# Use JAVA_HOME if set, otherwise look for java in PATH +if [ -x $JAVA_HOME/bin/java ]; then + JAVA=$JAVA_HOME/bin/java +else + JAVA=`which java` +fi + +if [ -z $CLASSPATH ]; then + echo "You must set the CLASSPATH var" >&2 + exit 1 +fi + +$JAVA -cp $CLASSPATH -Dlog4j.configuration=log4j-tools.properties \ + org.apache.cassandra.tools.SchemaTool "$@" + +# vi:ai sw=4 ts=4 tw=0 et diff --git a/src/java/org/apache/cassandra/config/Converter.java b/src/java/org/apache/cassandra/config/Converter.java index 9f8597ddd9..329a926e8b 100644 --- a/src/java/org/apache/cassandra/config/Converter.java +++ b/src/java/org/apache/cassandra/config/Converter.java @@ -12,6 +12,7 @@ import javax.xml.transform.TransformerException; import javax.xml.xpath.XPathExpressionException; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.SkipNullRepresenter; import org.apache.cassandra.utils.XMLUtils; import org.apache.cassandra.db.ColumnFamilyType; import org.w3c.dom.NodeList; @@ -319,18 +320,4 @@ public class Converter } } - - /* used to prevent null values from being included in generated YAML */ - private static class SkipNullRepresenter extends Representer { - protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, - Object propertyValue, Tag customTag) { - if (propertyValue == null) { - return null; - } else { - return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); - } - } - - } - } diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index b066148fa0..672dfa4788 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -29,6 +29,13 @@ import java.util.concurrent.*; import javax.management.MBeanServer; import javax.management.ObjectName; +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.RawColumnDefinition; +import org.apache.cassandra.config.RawColumnFamily; +import org.apache.cassandra.config.RawKeyspace; +import org.apache.cassandra.utils.SkipNullRepresenter; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -67,6 +74,13 @@ import org.apache.cassandra.thrift.UnavailableException; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.WrappedRunnable; import org.apache.log4j.Level; +import org.yaml.snakeyaml.Dumper; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.introspector.Property; +import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.representer.Representer; /* * This abstraction contains the token/identifier of this node @@ -1677,6 +1691,71 @@ public class StorageService implements IEndpointStateChangeSubscriber, StorageSe } + public String exportSchema() throws IOException + { + List keyspaces = new ArrayList(); + for (String ksname : DatabaseDescriptor.getNonSystemTables()) + { + KSMetaData ksm = DatabaseDescriptor.getTableDefinition(ksname); + RawKeyspace rks = new RawKeyspace(); + rks.name = ksm.name; + rks.replica_placement_strategy = ksm.strategyClass.getName(); + rks.replication_factor = ksm.replicationFactor; + rks.column_families = new RawColumnFamily[ksm.cfMetaData().size()]; + int i = 0; + for (CFMetaData cfm : ksm.cfMetaData().values()) + { + RawColumnFamily rcf = new RawColumnFamily(); + rcf.name = cfm.cfName; + rcf.compare_with = cfm.comparator.getClass().getName(); + rcf.compare_subcolumns_with = cfm.subcolumnComparator == null ? null : cfm.subcolumnComparator.getClass().getName(); + rcf.clock_type = cfm.clockType; + rcf.column_type = cfm.cfType; + rcf.comment = cfm.comment; + rcf.keys_cached = cfm.keyCacheSize; + rcf.preload_row_cache = cfm.preloadRowCache; + rcf.read_repair_chance = cfm.readRepairChance; + rcf.reconciler = cfm.reconciler.getClass().getName(); + rcf.rows_cached = cfm.rowCacheSize; + rcf.column_metadata = new RawColumnDefinition[cfm.column_metadata.size()]; + int j = 0; + for (ColumnDefinition cd : cfm.column_metadata.values()) + { + RawColumnDefinition rcd = new RawColumnDefinition(); + rcd.index_name = cd.index_name; + rcd.index_type = cd.index_type; + rcd.name = new String(cd.name, "UTF8"); + rcd.validator_class = cd.validator.getClass().getName(); + rcf.column_metadata[j++] = rcd; + } + if (j == 0) + rcf.column_metadata = null; + rks.column_families[i++] = rcf; + } + // whew. + keyspaces.add(rks); + } + + DumperOptions options = new DumperOptions(); + /* Use a block YAML arrangement */ + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + SkipNullRepresenter representer = new SkipNullRepresenter(); + /* Use Tag.MAP to avoid the class name being included as global tag */ + representer.addClassTag(RawColumnFamily.class, Tag.MAP); + representer.addClassTag(Keyspaces.class, Tag.MAP); + representer.addClassTag(ColumnDefinition.class, Tag.MAP); + Dumper dumper = new Dumper(representer, options); + Yaml yaml = new Yaml(dumper); + Keyspaces ks = new Keyspaces(); + ks.keyspaces = keyspaces; + return yaml.dump(ks); + } + + public class Keyspaces + { + public List keyspaces; + } + // Never ever do this at home. Used by tests. Map setReplicationStrategyUnsafe(Map replacement) { diff --git a/src/java/org/apache/cassandra/service/StorageServiceMBean.java b/src/java/org/apache/cassandra/service/StorageServiceMBean.java index ad941668fd..0962977916 100644 --- a/src/java/org/apache/cassandra/service/StorageServiceMBean.java +++ b/src/java/org/apache/cassandra/service/StorageServiceMBean.java @@ -206,6 +206,12 @@ public interface StorageServiceMBean */ public void loadSchemaFromYAML() throws ConfigurationException, IOException; + /** + * Introduced in 0.7 to allow schema yaml to be exported. + * @todo: deprecate in 0.7+1, remove in 0.7+2. + */ + public String exportSchema() throws IOException; + /** * Truncates (deletes) the given columnFamily from the provided keyspace. * Calling truncate results in actual deletion of all data in the cluster diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java b/src/java/org/apache/cassandra/tools/NodeProbe.java index 57856e8bd8..3ae92557dd 100644 --- a/src/java/org/apache/cassandra/tools/NodeProbe.java +++ b/src/java/org/apache/cassandra/tools/NodeProbe.java @@ -39,6 +39,7 @@ import javax.management.remote.JMXServiceURL; import org.apache.cassandra.cache.JMXInstrumentedCacheMBean; import org.apache.cassandra.concurrent.IExecutorMBean; +import org.apache.cassandra.config.ConfigurationException; import org.apache.cassandra.db.ColumnFamilyStoreMBean; import org.apache.cassandra.db.CompactionManager; import org.apache.cassandra.db.CompactionManagerMBean; @@ -407,6 +408,17 @@ public class NodeProbe throw new RuntimeException("Error while executing truncate", e); } } + + @Deprecated + public void loadSchemaFromYAML() throws ConfigurationException, IOException + { + ssProxy.loadSchemaFromYAML(); + } + + public String exportSchemaToYAML() throws IOException + { + return ssProxy.exportSchema(); + } } class ColumnFamilyStoreMBeanIterator implements Iterator> diff --git a/src/java/org/apache/cassandra/tools/SchemaTool.java b/src/java/org/apache/cassandra/tools/SchemaTool.java new file mode 100644 index 0000000000..7db00bd886 --- /dev/null +++ b/src/java/org/apache/cassandra/tools/SchemaTool.java @@ -0,0 +1,62 @@ +package org.apache.cassandra.tools; +/* + * + * 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. + * + */ + +import org.apache.cassandra.config.ConfigurationException; +import java.io.IOException; + +public class SchemaTool +{ + public static void main(String[] args) + throws NumberFormatException, IOException, InterruptedException, ConfigurationException + { + if (args.length != 3) + usage(); + + System.out.println("# Note: This tool is deprecated and will be removed in future releases."); + + String host = args[0]; + int port = 0; + + try + { + port = Integer.parseInt(args[1]); + } + catch (NumberFormatException e) + { + System.err.println("Port must be a number."); + System.exit(1); + } + + if ("import".equals(args[2])) + new NodeProbe(host, port).loadSchemaFromYAML(); + else if ("export".equals(args[2])) + System.out.println(new NodeProbe(host, port).exportSchemaToYAML()); + else + usage(); + } + + private static void usage() + { + System.err.printf("java %s import|export%n", SchemaTool.class.getName()); + System.exit(1); + } +} diff --git a/src/java/org/apache/cassandra/utils/SkipNullRepresenter.java b/src/java/org/apache/cassandra/utils/SkipNullRepresenter.java new file mode 100644 index 0000000000..184e68f8e0 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/SkipNullRepresenter.java @@ -0,0 +1,41 @@ +package org.apache.cassandra.utils; + +import org.yaml.snakeyaml.introspector.Property; +import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.representer.Representer; + +/** + * 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. + */ + + +/* used to prevent null values from being included in generated YAML */ +public class SkipNullRepresenter extends Representer +{ + protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) + { + if (propertyValue == null) + { + return null; + } + else + { + return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); + } + } +} \ No newline at end of file