From e8ce42609349228caa271873c5af8ff8050b4af9 Mon Sep 17 00:00:00 2001 From: chrislovecnm Date: Thu, 28 Apr 2016 13:26:45 -0600 Subject: [PATCH] Refactored KubernetesSeedProvider and added unit tests. Updated Docker image and bumped Cassandra version --- examples/cassandra/README.md | 88 ++++-- examples/cassandra/image/Dockerfile | 37 +-- examples/cassandra/image/Makefile | 4 +- examples/cassandra/image/cassandra.list | 6 +- examples/cassandra/image/cassandra.yaml | 56 ++-- .../cassandra/image/kubernetes-cassandra.jar | Bin 8558 -> 9796 bytes examples/cassandra/java/.gitignore | 1 + examples/cassandra/java/pom.xml | 136 ++++++--- .../k8s/cassandra/KubernetesSeedProvider.java | 175 ----------- .../k8s/cassandra/KubernetesSeedProvider.java | 274 ++++++++++++++++++ .../cassandra/KubernetesSeedProviderTest.java | 64 ++++ .../java/src/test/resources/cassandra.yaml | 57 ++++ .../java/src/test/resources/logback-test.xml | 34 +++ 13 files changed, 642 insertions(+), 290 deletions(-) create mode 100644 examples/cassandra/java/.gitignore delete mode 100644 examples/cassandra/java/src/io/k8s/cassandra/KubernetesSeedProvider.java create mode 100644 examples/cassandra/java/src/main/java/io/k8s/cassandra/KubernetesSeedProvider.java create mode 100644 examples/cassandra/java/src/test/java/io/k8s/cassandra/KubernetesSeedProviderTest.java create mode 100644 examples/cassandra/java/src/test/resources/cassandra.yaml create mode 100644 examples/cassandra/java/src/test/resources/logback-test.xml diff --git a/examples/cassandra/README.md b/examples/cassandra/README.md index 46977d4c63e..3e27718bc20 100644 --- a/examples/cassandra/README.md +++ b/examples/cassandra/README.md @@ -38,6 +38,7 @@ Documentation for other releases can be found at ## Table of Contents - [Prerequisites](#prerequisites) + - [Cassandra Docker](#cassandra-docker) - [tl;dr Quickstart](#tldr-quickstart) - [Step 1: Create a Cassandra Service](#step-1-create-a-cassandra-service) - [Step 2: Use a Replication Controller to create Cassandra node pods](#step-2-use-a-replication-controller-to-create-cassandra-node-pods) @@ -74,6 +75,38 @@ This example also has a few code and configuration files needed. To avoid typing these out, you can `git clone` the Kubernetes repository to your local computer. +## Cassandra Docker + +The pods use the [```gcr.io/google-samples/cassandra:v9```](image/Dockerfile) +image from Google's [container registry](https://cloud.google.com/container-registry/docs/). +The docker is based on `debian:jessie` and includes OpenJDK 8. This image +includes a standard Cassandra installation from the Apache Debian repo. + +### Custom Seed Provider + +A custom [`SeedProvider`](https://svn.apache.org/repos/asf/cassandra/trunk/src/java/org/apache/cassandra/locator/SeedProvider.java) +is included for running Cassandra on top of Kubernetes. In Cassandra, a +`SeedProvider` bootstraps the gossip protocol that Cassandra uses to find other +Cassandra nodes. Seed addresses are hosts deemed as contact points. Cassandra +instances use the seed list to find each other and learn the topology of the +ring. The [`KubernetesSeedProvider`](java/src/main/java/io/k8s/cassandra/KubernetesSeedProvider.java) +discovers Cassandra seeds IP addresses vis the Kubernetes API, those Cassandra +instances are defined within the Cassandra Service. + +Refer to the custom seed provider [README](java/README.md) for further +`KubernetesSeedProvider` configurations. For this example you should not need +to customize the Seed Provider configurations. + +See the [image](image/) directory of this example for specifics on +how the container docker image was built and what it contains. + +You may also note that we are setting some Cassandra parameters (`MAX_HEAP_SIZE` +and `HEAP_NEWSIZE`), and adding information about the +[namespace](../../docs/user-guide/namespaces.md). +We also tell Kubernetes that the container exposes +both the `CQL` and `Thrift` API ports. Finally, we tell the cluster +manager that we need 0.1 cpu (0.1 core). + ## tl;dr Quickstart If you want to jump straight to the commands we will run, @@ -103,8 +136,6 @@ kubectl delete service -l app=cassandra kubectl delete daemonset cassandra ``` - - ## Step 1: Create a Cassandra Service A Kubernetes _[Service](../../docs/user-guide/services.md)_ describes a set of @@ -239,38 +270,24 @@ by the Service." The `replicas` attribute specifies the desired number of replicas, in this case 2 initially. We'll scale up to more shortly. -The replica's pods are using the [```gcr.io/google-samples/cassandra:v8```](image/Dockerfile) -image from Google's [container registry](https://cloud.google.com/container-registry/docs/). -This is a standard Cassandra installation on top of Debian. However, it also -adds a custom -[`SeedProvider`](https://svn.apache.org/repos/asf/cassandra/trunk/src/java/org/apache/cassandra/locator/SeedProvider.java) to Cassandra. In -Cassandra, a ```SeedProvider``` bootstraps the gossip protocol that Cassandra -uses to find other nodes. -The [`KubernetesSeedProvider`](java/src/io/k8s/cassandra/KubernetesSeedProvider.java) -discovers the Kubernetes API Server using the built in Kubernetes -discovery service, and then uses the Kubernetes API to find new nodes. -See the [image](image/) directory of this example for specifics on -how the container image was built and what it contains. -You may also note that we are setting some Cassandra parameters (`MAX_HEAP_SIZE` -and `HEAP_NEWSIZE`), and adding information about the -[namespace](../../docs/user-guide/namespaces.md). -We also tell Kubernetes that the container exposes -both the `CQL` and `Thrift` API ports. Finally, we tell the cluster -manager that we need 0.1 cpu (0.1 core). Create the Replication Controller: ```console + $ kubectl create -f examples/cassandra/cassandra-controller.yaml + ``` You can list the new controller: ```console + $ kubectl get rc -o wide NAME DESIRED CURRENT AGE CONTAINER(S) IMAGE(S) SELECTOR cassandra 2 2 11s cassandra gcr.io/google-samples/cassandra:v8 app=cassandra + ``` Now if you list the pods in your cluster, and filter to the label @@ -278,10 +295,12 @@ Now if you list the pods in your cluster, and filter to the label you see which Kubernetes nodes the pods were scheduled onto.) ```console + $ kubectl get pods -l="app=cassandra" -o wide NAME READY STATUS RESTARTS AGE NODE cassandra-21qyy 1/1 Running 0 1m kubernetes-minion-b286 cassandra-q6sz7 1/1 Running 0 1m kubernetes-minion-9ye5 + ``` Because these pods have the label `app=cassandra`, they map to the service we @@ -290,6 +309,7 @@ defined in Step 1. You can check that the Pods are visible to the Service using the following service endpoints query: ```console + $ kubectl get endpoints cassandra -o yaml apiVersion: v1 kind: Endpoints @@ -314,6 +334,7 @@ subsets: ports: - port: 9042 protocol: TCP + ``` To show that the `SeedProvider` logic is working as intended, you can use the @@ -323,6 +344,7 @@ Cassandra pods. Again, substitute `cassandra-xxxxx` with the actual name of one of your pods. ```console + $ kubectl exec -ti cassandra-xxxxx -- nodetool status Datacenter: datacenter1 ======================= @@ -331,6 +353,7 @@ Status=Up/Down -- Address Load Tokens Owns (effective) Host ID Rack UN 10.244.0.5 74.09 KB 256 100.0% 86feda0f-f070-4a5b-bda1-2eeb0ad08b77 rack1 UN 10.244.3.3 51.28 KB 256 100.0% dafe3154-1d67-42e1-ac1d-78e7e80dce2b rack1 + ``` ## Step 3: Scale up the Cassandra cluster @@ -339,24 +362,29 @@ Now let's scale our Cassandra cluster to 4 pods. We do this by telling the Replication Controller that we now want 4 replicas. ```sh + $ kubectl scale rc cassandra --replicas=4 + ``` You can see the new pods listed: ```console + $ kubectl get pods -l="app=cassandra" -o wide NAME READY STATUS RESTARTS AGE NODE cassandra-21qyy 1/1 Running 0 6m kubernetes-minion-b286 cassandra-81m2l 1/1 Running 0 47s kubernetes-minion-b286 cassandra-8qoyp 1/1 Running 0 47s kubernetes-minion-9ye5 cassandra-q6sz7 1/1 Running 0 6m kubernetes-minion-9ye5 + ``` In a few moments, you can examine the Cassandra cluster status again, and see that the new pods have been detected by the custom `SeedProvider`: ```console + $ kubectl exec -ti cassandra-xxxxx -- nodetool status Datacenter: datacenter1 ======================= @@ -367,6 +395,7 @@ UN 10.244.0.6 51.67 KB 256 48.9% d07b23a5-56a1-4b0b-952d-68a UN 10.244.1.5 84.71 KB 256 50.7% e060df1f-faa2-470c-923d-ca049b0f3f38 rack1 UN 10.244.1.6 84.71 KB 256 47.0% 83ca1580-4f3c-4ec5-9b38-75036b7a297f rack1 UN 10.244.0.5 68.2 KB 256 53.4% 72ca27e2-c72c-402a-9313-1e4b61c2f839 rack1 + ``` ## Step 4: Delete the Replication Controller @@ -374,7 +403,9 @@ UN 10.244.0.5 68.2 KB 256 53.4% 72ca27e2-c72c-402a-9313-1e4 Before you start Step 5, __delete the replication controller__ you created above: ```sh + $ kubectl delete rc cassandra + ``` ## Step 5: Use a DaemonSet instead of a Replication Controller @@ -459,21 +490,27 @@ pod relationship. Create this daemonset: ```console + $ kubectl create -f examples/cassandra/cassandra-daemonset.yaml + ``` You may need to disable config file validation, like so: ```console + $ kubectl create -f examples/cassandra/cassandra-daemonset.yaml --validate=false + ``` You can see the daemonset running: ```console + $ kubectl get daemonset NAME DESIRED CURRENT NODE-SELECTOR cassandra 3 3 + ``` Now, if you list the pods in your cluster, and filter to the label @@ -481,11 +518,13 @@ Now, if you list the pods in your cluster, and filter to the label node in your network. ```console + $ kubectl get pods -l="app=cassandra" -o wide NAME READY STATUS RESTARTS AGE NODE cassandra-ico4r 1/1 Running 0 4s kubernetes-minion-rpo1 cassandra-kitfh 1/1 Running 0 1s kubernetes-minion-9ye5 cassandra-tzw89 1/1 Running 0 2s kubernetes-minion-b286 + ``` To prove that this all worked as intended, you can again use the `nodetool` @@ -493,6 +532,7 @@ command to examine the status of the cluster. To do this, use the `kubectl exec` command to run `nodetool` in one of your newly-launched cassandra pods. ```console + $ kubectl exec -ti cassandra-xxxxx -- nodetool status Datacenter: datacenter1 ======================= @@ -502,6 +542,7 @@ Status=Up/Down UN 10.244.0.5 74.09 KB 256 100.0% 86feda0f-f070-4a5b-bda1-2eeb0ad08b77 rack1 UN 10.244.4.2 32.45 KB 256 100.0% 0b1be71a-6ffb-4895-ac3e-b9791299c141 rack1 UN 10.244.3.3 51.28 KB 256 100.0% dafe3154-1d67-42e1-ac1d-78e7e80dce2b rack1 + ``` **Note**: This example had you delete the cassandra Replication Controller before @@ -518,15 +559,12 @@ both together by using additional labels and selectors. When you are ready to take down your resources, do the following: ```console + $ kubectl delete service -l app=cassandra $ kubectl delete daemonset cassandra + ``` -## Seed Provider Source - -The Seed Provider source is -[here](java/src/io/k8s/cassandra/KubernetesSeedProvider.java). - [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/examples/cassandra/README.md?pixel)]() diff --git a/examples/cassandra/image/Dockerfile b/examples/cassandra/image/Dockerfile index d40c53ba085..c7643a7476e 100644 --- a/examples/cassandra/image/Dockerfile +++ b/examples/cassandra/image/Dockerfile @@ -12,32 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM google/debian:wheezy +FROM google/debian:jessie COPY cassandra.list /etc/apt/sources.list.d/cassandra.list +COPY run.sh /run.sh -RUN gpg --keyserver pgp.mit.edu --recv-keys F758CE318D77295D -RUN gpg --export --armor F758CE318D77295D | apt-key add - - -RUN gpg --keyserver pgp.mit.edu --recv-keys 2B5C1B00 -RUN gpg --export --armor 2B5C1B00 | apt-key add - - -RUN gpg --keyserver pgp.mit.edu --recv-keys 0353B12C -RUN gpg --export --armor 0353B12C | apt-key add - - -RUN apt-get update -RUN apt-get -qq -y install procps cassandra +RUN gpg --keyserver pgp.mit.edu --recv-keys F758CE318D77295D && \ + gpg --export --armor F758CE318D77295D | apt-key add - && \ + gpg --keyserver pgp.mit.edu --recv-keys 2B5C1B00 && \ + gpg --export --armor 2B5C1B00 | apt-key add - && \ + gpg --keyserver pgp.mit.edu --recv-keys 0353B12C && \ + gpg --export --armor 0353B12C | apt-key add - && \ + apt-get update && \ + apt-get -qq -y install procps cassandra openjdk-8-jre-headless && \ + chmod a+rx /run.sh && \ + mkdir -p /cassandra_data/data && \ + chown -R cassandra.cassandra /etc/cassandra /cassandra_data && \ + chmod o+w -R /etc/cassandra /cassandra_data && \ + rm -rf /var/lib/apt/lists/* && \ + rm -rf /usr/share/doc/ && \ + rm -rf /usr/share/doc-base/ && \ + rm -rf /usr/share/man/ && \ + rm -rf /tmp/* COPY cassandra.yaml /etc/cassandra/cassandra.yaml COPY logback.xml /etc/cassandra/logback.xml -COPY run.sh /run.sh COPY kubernetes-cassandra.jar /kubernetes-cassandra.jar -RUN chmod a+rx /run.sh && \ - mkdir -p /cassandra_data/data && \ - chown -R cassandra.cassandra /etc/cassandra /cassandra_data && \ - chmod o+w -R /etc/cassandra /cassandra_data - VOLUME ["/cassandra_data/data"] USER cassandra diff --git a/examples/cassandra/image/Makefile b/examples/cassandra/image/Makefile index 7720c46710b..1275826a898 100644 --- a/examples/cassandra/image/Makefile +++ b/examples/cassandra/image/Makefile @@ -14,11 +14,11 @@ # build the cassandra image. -VERSION=v8 +VERSION=v9 all: build -kubernetes-cassandra.jar: ../java/* ../java/src/io/k8s/cassandra/*.java +kubernetes-cassandra.jar: ../java/* ../java/src/main/java/io/k8s/cassandra/*.java cd ../java && mvn package mv ../java/target/kubernetes-cassandra*.jar kubernetes-cassandra.jar cd ../java && mvn clean diff --git a/examples/cassandra/image/cassandra.list b/examples/cassandra/image/cassandra.list index 02e06f2d1ea..f82805c7aa1 100644 --- a/examples/cassandra/image/cassandra.list +++ b/examples/cassandra/image/cassandra.list @@ -1,3 +1,5 @@ -deb http://www.apache.org/dist/cassandra/debian 21x main -deb-src http://www.apache.org/dist/cassandra/debian 21x main +deb http://www.apache.org/dist/cassandra/debian 34x main +deb-src http://www.apache.org/dist/cassandra/debian 34x main +# for jre8 +deb http://http.debian.net/debian jessie-backports main diff --git a/examples/cassandra/image/cassandra.yaml b/examples/cassandra/image/cassandra.yaml index c5df4cadf0e..41b85a84d52 100644 --- a/examples/cassandra/image/cassandra.yaml +++ b/examples/cassandra/image/cassandra.yaml @@ -1,4 +1,4 @@ -# Cassandra storage config YAML +# Cassandra storage config YAML # NOTE: # See http://wiki.apache.org/cassandra/StorageConfiguration for @@ -20,13 +20,13 @@ cluster_name: 'Test Cluster' # Specifying initial_token will override this setting on the node's initial start, # on subsequent starts, this setting will apply even if initial token is set. # -# If you already have a cluster with 1 token per node, and wish to migrate to +# If you already have a cluster with 1 token per node, and wish to migrate to # multiple tokens per node, see http://wiki.apache.org/cassandra/Operations num_tokens: 256 # initial_token allows you to specify tokens manually. While you can use # it with -# vnodes (num_tokens > 1, above) -- in which case you should provide a -# comma-separated list -- it's primarily used when adding nodes # to legacy clusters +# vnodes (num_tokens > 1, above) -- in which case you should provide a +# comma-separated list -- it's primarily used when adding nodes # to legacy clusters # that do not have vnodes enabled. # initial_token: @@ -157,7 +157,7 @@ key_cache_save_period: 14400 # NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. # # Default value is 0, to disable row caching. -row_cache_size_in_mb: 0 +row_cache_size_in_mb: 16 # Duration in seconds after which Cassandra should # save the row cache. Caches are saved to saved_caches_directory as specified @@ -204,7 +204,7 @@ counter_cache_save_period: 7200 # well as caches. Experiments show that JEMAlloc saves some memory # than the native GCC allocator (i.e., JEMalloc is more # fragmentation-resistant). -# +# # Supported values are: NativeAllocator, JEMallocAllocator # # If you intend to use JEMallocAllocator you have to install JEMalloc as library and @@ -217,14 +217,14 @@ counter_cache_save_period: 7200 # If not set, the default directory is $CASSANDRA_HOME/data/saved_caches. saved_caches_directory: /cassandra_data/saved_caches -# commitlog_sync may be either "periodic" or "batch." +# commitlog_sync may be either "periodic" or "batch." # When in batch mode, Cassandra won't ack writes until the commit log # has been fsynced to disk. It will wait up to # commitlog_sync_batch_window_in_ms milliseconds for other writes, before # performing the sync. # # commitlog_sync: batch -# commitlog_sync_batch_window_in_ms: 50 +# commitlog_sync_batch_window_in_ms: 1.0 # # the other option is "periodic" where writes may be acked immediately # and the CommitLog is simply synced every commitlog_sync_period_in_ms @@ -239,7 +239,7 @@ commitlog_sync_period_in_ms: 10000 # The size of the individual commitlog file segments. A commitlog # segment may be archived, deleted, or recycled once all the data # in it (potentially from each columnfamily in the system) has been -# flushed to sstables. +# flushed to sstables. # # The default size is 32, which is almost always fine, but if you are # archiving commitlog segments (see commitlog_archiving.properties), @@ -250,11 +250,11 @@ commitlog_segment_size_in_mb: 32 # any class that implements the SeedProvider interface and has a # constructor that takes a Map of parameters will do. seed_provider: - # Addresses of hosts that are deemed contact points. + # Addresses of hosts that are deemed contact points. # Cassandra nodes use this list of hosts to find each other and learn # the topology of the ring. You must change this if you are running # multiple nodes! - - class_name: io.k8s.cassandra.KubernetesSeedProvider + - class_name: io.k8s.cassandra.KubernetesSeedProvider parameters: # seeds is actually a comma-delimited list of addresses. # Ex: ",," @@ -279,7 +279,7 @@ concurrent_counter_writes: 32 # the smaller of 1/4 of heap or 512MB. # file_cache_size_in_mb: 512 -# Total permitted memory to use for memtables. Cassandra will stop +# Total permitted memory to use for memtables. Cassandra will stop # accepting writes when the limit is exceeded until a flush completes, # and will trigger a flush based on memtable_cleanup_threshold # If omitted, Cassandra will set both to 1/4 the size of the heap. @@ -300,7 +300,7 @@ concurrent_counter_writes: 32 # heap_buffers: on heap nio buffers # offheap_buffers: off heap (direct) nio buffers # offheap_objects: native memory, eliminating nio buffer heap overhead -memtable_allocation_type: heap_buffers +memtable_allocation_type: offheap_objects # Total space to use for commitlogs. Since commitlog segments are # mmapped, and hence use up address space, the default size is 32 @@ -314,11 +314,11 @@ memtable_allocation_type: heap_buffers # This sets the amount of memtable flush writer threads. These will # be blocked by disk io, and each one will hold a memtable in memory -# while blocked. +# while blocked. # # memtable_flush_writers defaults to the smaller of (number of disks, # number of cores), with a minimum of 2 and a maximum of 8. -# +# # If your data directories are backed by SSD, you should increase this # to the number of cores. #memtable_flush_writers: 8 @@ -476,7 +476,7 @@ thrift_framed_transport_size_in_mb: 15 # flushed or streamed locally in a backups/ subdirectory of the # keyspace data. Removing these links is the operator's # responsibility. -incremental_backups: false +incremental_backups: true # Whether or not to take a snapshot before each compaction. Be # careful using this option, since Cassandra won't clean up the @@ -485,7 +485,7 @@ incremental_backups: false snapshot_before_compaction: false # Whether or not a snapshot is taken of the data before keyspace truncation -# or dropping of column families. The STRONGLY advised default of true +# or dropping of column families. The STRONGLY advised default of true # should be used to provide data safety. If you set this flag to false, you will # lose data on truncation or drop. auto_snapshot: true @@ -529,9 +529,10 @@ batch_size_warn_threshold_in_kb: 5 # # concurrent_compactors defaults to the smaller of (number of disks, # number of cores), with a minimum of 2 and a maximum of 8. -# +# # If your data directories are backed by SSD, you should increase this # to the number of cores. +# TODO: set this based on env?? #concurrent_compactors: 1 # Throttles compaction to the given total throughput across the entire @@ -544,7 +545,7 @@ compaction_throughput_mb_per_sec: 16 # When compacting, the replacement sstable(s) can be opened before they # are completely written, and used in place of the prior sstables for -# any range that has been written. This helps to smoothly transfer reads +# any range that has been written. This helps to smoothly transfer reads # between the sstables, reducing page cache churn and keeping hot rows hot sstable_preemptive_open_interval_in_mb: 50 @@ -582,7 +583,7 @@ request_timeout_in_ms: 10000 # Enable operation timeout information exchange between nodes to accurately # measure request timeouts. If disabled, replicas will assume that requests # were forwarded to them instantly by the coordinator, which means that -# under overload conditions we will waste that much extra time processing +# under overload conditions we will waste that much extra time processing # already-timed-out requests. # # Warning: before enabling this property make sure to ntp is installed @@ -654,7 +655,7 @@ endpoint_snitch: SimpleSnitch # controls how often to perform the more expensive part of host score # calculation -dynamic_snitch_update_interval_in_ms: 100 +dynamic_snitch_update_interval_in_ms: 100 # controls how often to reset all host scores, allowing a bad host to # possibly recover dynamic_snitch_reset_interval_in_ms: 600000 @@ -678,13 +679,15 @@ dynamic_snitch_badness_threshold: 0.1 # client requests to a node with a separate queue for each # request_scheduler_id. The scheduler is further customized by # request_scheduler_options as described below. -request_scheduler: org.apache.cassandra.scheduler.NoScheduler +request_scheduler: org.apache.cassandra.scheduler.RoundRobinScheduler +request_scheduler_id: keyspace + # Scheduler Options vary based on the type of scheduler # NoScheduler - Has no options # RoundRobin # - throttle_limit -- The throttle_limit is the number of in-flight -# requests per client. Requests beyond +# requests per client. Requests beyond # that limit are queued up until # running requests can complete. # The value of 80 here is twice the number of @@ -762,3 +765,10 @@ internode_compression: all # reducing overhead from the TCP protocol itself, at the cost of increasing # latency if you block for cross-datacenter responses. inter_dc_tcp_nodelay: false + +disk_access_mode: mmap +row_cache_class_name: org.apache.cassandra.cache.OHCProvider + +# Not till 3.5 +#enable_user_defined_functions: true +#enable_scripted_user_defined_functions: tru diff --git a/examples/cassandra/image/kubernetes-cassandra.jar b/examples/cassandra/image/kubernetes-cassandra.jar index 3292306c8b0c9fa5d668165f2cd0c44709db59ff..7d8c6a86b3fe13f16645596996369dc680df59fa 100644 GIT binary patch delta 8151 zcmZWu1yoc~x2AItkZy+V?(Q7IAq0l*mJ%2S7(rTk2J~jqz z3vNUEkKf-8(>^m|{vkhE{tyHeIv@U@V`5QMx`YT2IvB33TxZP2mBGPN-XB>n0dr{F zhfg(+W9y3a4tZ3*Pv(Eh0tk z#8?CcgK*A>KIDTPr=JlUfm=eWnIV#35o)b6BY2mY)+>DDmJg{Q&Q+|B2NpAlW3bw{ z$o}#NzZ@&_N7&x5+pPNTSq0hXzoJ6uBsRwuvN&{h;?kwacz(aI(ALdsdZ`q{TK^>J zfhIDB%#Cl)Z0I8i*T~1@AYxABaCclcMIeS=i98%^#*`LTL9REqn7k1MaOs$IcNsO` z(Tsm|s#57?`6~!^P{G#qV!MQ(^yRzL!DQC%D2PeM=i1UZuzTIn7I{xQK5A|N z-Gs(%#6_rzIp=BOQ6@Ke+pN-$pWyen-4Vj_XaY&IzcoCq%#dE66kaGSGjT!rTQFu9 z1YzqUZ65m)Ra%YpI&B?N{i{ash|&I(!wcsUqi%FGG!0C|BXj|{h=7Bqoxgt)*vo@V znIxP;+TSfvV&=lsqZS)sYDubym&QV_eq7V5tq?~p5#-Ty*3OH{F1{ZU3@7O!zC(j& zNWt(Eo}@y&1DylE<+wOsEM5rOpfB~jW1o66qY|YnEHGe};v4;mYhdBYSC&&Uuf9B# zwc{6 z2GqocnLf-LvgayzaK1!??H+aR;E=J%-}=nnANDyRuLX0m+V9sHEb=>htRIg$a4bSg zw{onf_-6-MK6i(pS}sTRPiu(i_ii?^fthR#Mw8RKVfK&ka7vKbts+TD=0g0)lq!RT z#kJ&g9QlwzqgldjK0C-pB$56{O(Jbs1*nabgg(Nvbrr>?SicQizN7me;y;to1s+*S= z9K$P2W8cA&!av{Xxx=%^u1#{1FM9F$Ff%DX#XZKY{AP9mt&jcj{R0#AHHAh{;4iYW z7>a!4xQEp9CcZsOPP1*_hzmTbHwg%ncQhh!#ecT~yuVrQnXY&ALeyca!6C}z@(8Lh zVGW0}nFVi4yafRna!WiNAQnM$PgaHV=jF}LkrB4z5Q9Tb{4`bCrb&e*{C=jsy^}FD!DaQnI4Ye!DL!_5}`2Cwmz!YQK7njo^Q=+eOf^ZxD(uV8KO;0uoWmwLaU6<^;CNH;B8h?gGhtJ5vRzi=Ppll~ykn3MI z;!0SsKypDLqbh4hrfY!D$o)1}+_WTU6m(Z51Wbhs+1!7S)2f?n%2sy45Xy&@tzKk~+>RG8{&FaVU{Xu-UONT6cM4zk`jkWR=U!ZFWyl$o z3vmo9LWA7=f5K*Ih*Mr!TnsiQ4G`>S*K-gQq>X6XiN6Ce4HKrpg(p=~?RG_sK#46T zp+te}ZJGM*8-BE0JRNOM36oTIF9wjOGzAMhE9YZBZru%|>;5E5nT)PBL(wkqv)Z1S zg_Kr7(GCcnt0?L8$sfTV>EK2CU?=Ed!Pp!$y?kXzQ&+jBevbBfm@rVWm45_`x%7d# z$3OZOMZ`K%O9iGb+)0^~cEZ?^Jy2Wg%vs5PjvjLV ze;Qr$siT8G2^v}t3mO_f8p4)I5ME%0A4)f!a}RI)K{K*JuamE4`DoBdHWn{RiCpCk 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 literal 8558 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 diff --git a/examples/cassandra/java/.gitignore b/examples/cassandra/java/.gitignore new file mode 100644 index 00000000000..eb5a316cbd1 --- /dev/null +++ b/examples/cassandra/java/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/cassandra/java/pom.xml b/examples/cassandra/java/pom.xml index 8a95eafd0b4..4c9824f9a9d 100644 --- a/examples/cassandra/java/pom.xml +++ b/examples/cassandra/java/pom.xml @@ -1,47 +1,93 @@ + - 4.0.0 - io.k8s.cassandra - kubernetes-cassandra - 0.0.5 - - src - - - maven-compiler-plugin - 2.3.2 - - 1.7 - 1.7 - - - - - - - junit - junit - 3.8.1 - test - - - org.slf4j - slf4j-log4j12 - 1.7.5 - - - org.codehaus.jackson - jackson-core-asl - 1.6.3 - - - org.codehaus.jackson - jackson-mapper-asl - 1.6.3 - - - org.apache.cassandra - cassandra-all - 2.0.11 - - + 4.0.0 + io.k8s.cassandra + kubernetes-cassandra + 1.0.0 + + + + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + + + 1.1.3 + + + + + junit + junit + 4.11 + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + org.slf4j + slf4j-api + 1.7.5 + provided + + + ch.qos.logback + logback-classic + ${logback.version} + provided + + + + ch.qos.logback + logback-core + ${logback.version} + provided + + + + org.codehaus.jackson + jackson-core-asl + 1.6.3 + provided + + + + org.codehaus.jackson + jackson-mapper-asl + 1.6.3 + provided + + + + org.apache.cassandra + cassandra-all + 3.4 + provided + + + diff --git a/examples/cassandra/java/src/io/k8s/cassandra/KubernetesSeedProvider.java b/examples/cassandra/java/src/io/k8s/cassandra/KubernetesSeedProvider.java deleted file mode 100644 index 04dd821a5a5..00000000000 --- a/examples/cassandra/java/src/io/k8s/cassandra/KubernetesSeedProvider.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2015 Google Inc. - * - * Licensed 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 io.k8s.cassandra; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.net.URL; -import java.net.URLConnection; -import java.security.cert.X509Certificate; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.annotate.JsonIgnoreProperties; -import org.codehaus.jackson.map.ObjectMapper; -import org.apache.cassandra.locator.SeedProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class KubernetesSeedProvider implements SeedProvider { - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Address { - public String ip; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Subset { - public List
addresses; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Endpoints { - public List subsets; - } - - private static String getEnvOrDefault(String var, String def) { - String val = System.getenv(var); - if (val == null) { - val = def; - } - return val; - } - - private static String getServiceAccountToken() throws IOException { - String file = "/var/run/secrets/kubernetes.io/serviceaccount/token"; - return new String(Files.readAllBytes(Paths.get(file))); - } - - private static final Logger logger = LoggerFactory.getLogger(KubernetesSeedProvider.class); - - private List defaultSeeds; - private TrustManager[] trustAll; - private HostnameVerifier trustAllHosts; - - public KubernetesSeedProvider(Map params) { - // Taken from SimpleSeedProvider.java - // These are used as a fallback, if we get nothing from k8s. - String[] hosts = params.get("seeds").split(",", -1); - defaultSeeds = new ArrayList(hosts.length); - for (String host : hosts) - { - try { - defaultSeeds.add(InetAddress.getByName(host.trim())); - } - catch (UnknownHostException ex) - { - // not fatal... DD will bark if there end up being zero seeds. - logger.warn("Seed provider couldn't lookup host " + host); - } - } - // TODO: Load the CA cert when it is available on all platforms. - trustAll = new TrustManager[] { - new X509TrustManager() { - public void checkServerTrusted(X509Certificate[] certs, String authType) {} - public void checkClientTrusted(X509Certificate[] certs, String authType) {} - public X509Certificate[] getAcceptedIssuers() { return null; } - } - }; - trustAllHosts = new HostnameVerifier() { - public boolean verify(String hostname, SSLSession session) { - return true; - } - }; - } - - public List getSeeds() { - List list = new ArrayList(); - //String host = "https://kubernetes.default.svc.cluster.local"; - String proto = "https://"; - String host = getEnvOrDefault("KUBERNETES_PORT_443_TCP_ADDR", "kubernetes.default.svc.cluster.local"); - String port = getEnvOrDefault("KUBERNETES_PORT_443_TCP_PORT", "443"); - String serviceName = getEnvOrDefault("CASSANDRA_SERVICE", "cassandra"); - String podNamespace = getEnvOrDefault("POD_NAMESPACE", "default"); - String path = String.format("/api/v1/namespaces/%s/endpoints/", podNamespace); - try { - String token = getServiceAccountToken(); - - SSLContext ctx = SSLContext.getInstance("SSL"); - ctx.init(null, trustAll, new SecureRandom()); - - URL url = new URL(proto + host + ":" + port + path + serviceName); - logger.info("Getting endpoints from " + url); - HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); - - // TODO: Remove this once the CA cert is propagated everywhere, and replace - // with loading the CA cert. - conn.setSSLSocketFactory(ctx.getSocketFactory()); - conn.setHostnameVerifier(trustAllHosts); - - conn.addRequestProperty("Authorization", "Bearer " + token); - ObjectMapper mapper = new ObjectMapper(); - Endpoints endpoints = mapper.readValue(conn.getInputStream(), Endpoints.class); - if (endpoints != null) { - // Here is a problem point, endpoints.subsets can be null in first node cases. - if (endpoints.subsets != null && !endpoints.subsets.isEmpty()){ - for (Subset subset : endpoints.subsets) { - if (subset.addresses != null && !subset.addresses.isEmpty()) { - for (Address address : subset.addresses) { - list.add(InetAddress.getByName(address.ip)); - } - } - } - } - logger.info("Available endpoints: " + list); - } else { - logger.warn("Endpoints are not available"); - } - } catch (IOException | NoSuchAlgorithmException | KeyManagementException ex) { - logger.warn("Request to kubernetes apiserver failed", ex); - } - if (list.size() == 0) { - // If we got nothing, we might be the first instance, in that case - // fall back on the seeds that were passed in cassandra.yaml. - return defaultSeeds; - } - return list; - } - - // Simple main to test the implementation - public static void main(String[] args) { - SeedProvider provider = new KubernetesSeedProvider(new HashMap()); - System.out.println(provider.getSeeds()); - } -} diff --git a/examples/cassandra/java/src/main/java/io/k8s/cassandra/KubernetesSeedProvider.java b/examples/cassandra/java/src/main/java/io/k8s/cassandra/KubernetesSeedProvider.java new file mode 100644 index 00000000000..e2111f26d56 --- /dev/null +++ b/examples/cassandra/java/src/main/java/io/k8s/cassandra/KubernetesSeedProvider.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2015 Google Inc. + * + * Licensed 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 io.k8s.cassandra; + +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.ConfigurationLoader; +import org.apache.cassandra.config.YamlConfigurationLoader; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.locator.SeedProvider; +import org.apache.cassandra.locator.SimpleSeedProvider; +import org.apache.cassandra.utils.FBUtilities; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.codehaus.jackson.map.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.*; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Self discovery {@link SeedProvider} that creates a list of Cassandra Seeds by + * communicating with the Kubernetes API. + *

Various System Variable can be used to configure this provider: + *

    + *
  • KUBERNETES_PORT_443_TCP_ADDR defaults to kubernetes.default.svc.cluster.local
  • + *
  • KUBERNETES_PORT_443_TCP_PORT defaults to 443
  • + *
  • CASSANDRA_SERVICE defaults to cassandra
  • + *
  • POD_NAMESPACE defaults to 'default'
  • + *
  • CASSANDRA_SERVICE_NUM_SEEDS defaults to 8 seeds
  • + *
+ */ +public class KubernetesSeedProvider implements SeedProvider { + + private static final Logger logger = LoggerFactory.getLogger(KubernetesSeedProvider.class); + + /** + * default seeds to fall back on + */ + private List defaultSeeds; + + + private TrustManager[] trustAll; + + private HostnameVerifier trustAllHosts; + + + /** + * Create new Seeds + * @param params + */ + public KubernetesSeedProvider(Map params) { + + // Create default seeds + defaultSeeds = createDefaultSeeds(); + + // TODO: Load the CA cert when it is available on all platforms. + trustAll = new TrustManager[] { + new X509TrustManager() { + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + public X509Certificate[] getAcceptedIssuers() { return null; } + } + }; + + trustAllHosts = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + } + + + /** + * Call kubernetes API to collect a list of seed providers + * @return list of seed providers + */ + public List getSeeds() { + + String host = getEnvOrDefault("KUBERNETES_PORT_443_TCP_ADDR", "kubernetes.default.svc.cluster.local"); + String port = getEnvOrDefault("KUBERNETES_PORT_443_TCP_PORT", "443"); + String serviceName = getEnvOrDefault("CASSANDRA_SERVICE", "cassandra"); + String podNamespace = getEnvOrDefault("POD_NAMESPACE", "default"); + String path = String.format("/api/v1/namespaces/%s/endpoints/", podNamespace); + String seedSizeVar = getEnvOrDefault("CASSANDRA_SERVICE_NUM_SEEDS", "8"); + Integer seedSize = Integer.valueOf(seedSizeVar); + + List seeds = new ArrayList(); + try { + String token = getServiceAccountToken(); + + SSLContext ctx = SSLContext.getInstance("SSL"); + ctx.init(null, trustAll, new SecureRandom()); + + String PROTO = "https://"; + URL url = new URL(PROTO + host + ":" + port + path + serviceName); + logger.info("Getting endpoints from " + url); + HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); + + // TODO: Remove this once the CA cert is propagated everywhere, and replace + // with loading the CA cert. + conn.setHostnameVerifier(trustAllHosts); + + conn.setSSLSocketFactory(ctx.getSocketFactory()); + conn.addRequestProperty("Authorization", "Bearer " + token); + ObjectMapper mapper = new ObjectMapper(); + Endpoints endpoints = mapper.readValue(conn.getInputStream(), Endpoints.class); + + if (endpoints != null) { + + // Here is a problem point, endpoints.subsets can be null in first node cases. + + if (endpoints.subsets != null && !endpoints.subsets.isEmpty()){ + + for (Subset subset : endpoints.subsets) { + if (subset.addresses != null && !subset.addresses.isEmpty()) { + + for (Address address : subset.addresses) { + seeds.add(InetAddress.getByName(address.ip)); + + if(seeds.size() >= seedSize) { + logger.info("Available num endpoints: " + seeds.size()); + return Collections.unmodifiableList(seeds); + } + } + } + } + + } + + logger.info("Available num endpoints: " + seeds.size()); + + } else { + + logger.warn("Endpoints are not available using default seeds in cassandra.yaml"); + return Collections.unmodifiableList(defaultSeeds); + + } + } catch (IOException | NoSuchAlgorithmException | KeyManagementException ex) { + logger.warn("Request to kubernetes apiserver failed, using default seeds in cassandra.yaml", ex); + return Collections.unmodifiableList(defaultSeeds); + } + + if (seeds.size() == 0) { + + // If we got nothing, we might be the first instance, in that case + // fall back on the seeds that were passed in cassandra.yaml. + logger.warn("Seeds are not available using default seeds in cassandra.yaml"); + return Collections.unmodifiableList(defaultSeeds); + + } + + return Collections.unmodifiableList(seeds); + } + + /** + * Code taken from {@link SimpleSeedProvider}. This is used as a fall back + * incase we don't find seeds + * @return + */ + protected List createDefaultSeeds() + { + Config conf; + try + { + conf = loadConfig(); + } + catch (Exception e) + { + throw new AssertionError(e); + } + String[] hosts = conf.seed_provider.parameters.get("seeds").split(",", -1); + List seeds = new ArrayList(); + for (String host : hosts) + { + try + { + seeds.add(InetAddress.getByName(host.trim())); + } + catch (UnknownHostException ex) + { + // not fatal... DD will bark if there end up being zero seeds. + logger.warn("Seed provider couldn't lookup host {}", host); + } + } + + if(seeds.size() == 0) { + try { + seeds.add(InetAddress.getLocalHost()); + } catch (UnknownHostException e) { + logger.warn("Seed provider couldn't lookup localhost"); + } + } + return Collections.unmodifiableList(seeds); + } + + /** + * Code taken from {@link SimpleSeedProvider} + * @return + */ + protected static Config loadConfig() throws ConfigurationException + { + String loaderClass = System.getProperty("cassandra.config.loader"); + ConfigurationLoader loader = loaderClass == null + ? new YamlConfigurationLoader() + : FBUtilities.construct(loaderClass, "configuration loading"); + return loader.loadConfig(); + } + + private static String getEnvOrDefault(String var, String def) { + String val = System.getenv(var); + if (val == null) { + val = def; + } + return val; + } + + private static String getServiceAccountToken() throws IOException { + String file = "/var/run/secrets/kubernetes.io/serviceaccount/token"; + try { + return new String(Files.readAllBytes(Paths.get(file))); + } catch (IOException e) { + logger.warn("unable to load service account token"); + throw e; + } + } + + protected List getDefaultSeeds() { + return defaultSeeds; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Address { + public String ip; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Subset { + public List
addresses; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static class Endpoints { + public List subsets; + } + + +} diff --git a/examples/cassandra/java/src/test/java/io/k8s/cassandra/KubernetesSeedProviderTest.java b/examples/cassandra/java/src/test/java/io/k8s/cassandra/KubernetesSeedProviderTest.java new file mode 100644 index 00000000000..987622f6650 --- /dev/null +++ b/examples/cassandra/java/src/test/java/io/k8s/cassandra/KubernetesSeedProviderTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 Google Inc. + * + * Licensed 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 io.k8s.cassandra; + +import com.google.common.collect.ImmutableMap; +import org.apache.cassandra.locator.SeedProvider; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.hamcrest.Matchers.*; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.*; + +public class KubernetesSeedProviderTest { + + private static final Logger logger = LoggerFactory.getLogger(KubernetesSeedProviderTest.class); + + @Test + @Ignore("has to be run inside of a kube cluster") + public void getSeeds() throws Exception { + SeedProvider provider = new KubernetesSeedProvider(new HashMap()); + List seeds = provider.getSeeds(); + + assertThat(seeds, is(not(empty()))); + + } + + @Test + public void testDefaultSeeds() throws Exception { + + KubernetesSeedProvider provider = new KubernetesSeedProvider(new HashMap()); + List seeds = provider.getDefaultSeeds(); + List seedsTest = new ArrayList<>(); + seedsTest.add(InetAddress.getByName("8.4.4.4")); + seedsTest.add(InetAddress.getByName("8.8.8.8")); + assertThat(seeds, is(not(empty()))); + assertThat(seeds, is(seedsTest)); + logger.debug("seeds loaded {}", seeds); + + } + + +} \ No newline at end of file diff --git a/examples/cassandra/java/src/test/resources/cassandra.yaml b/examples/cassandra/java/src/test/resources/cassandra.yaml new file mode 100644 index 00000000000..791d310364f --- /dev/null +++ b/examples/cassandra/java/src/test/resources/cassandra.yaml @@ -0,0 +1,57 @@ +# Copyright (C) 2015 Google Inc. +# +# Licensed 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. +# +# Warning! +# Consider the effects on 'o.a.c.i.s.LegacySSTableTest' before changing schemas in this file. +# +cluster_name: Test Cluster +# memtable_allocation_type: heap_buffers +memtable_allocation_type: offheap_objects +commitlog_sync: batch +commitlog_sync_batch_window_in_ms: 1.0 +commitlog_segment_size_in_mb: 5 +commitlog_directory: target/cassandra/commitlog +hints_directory: target/cassandra/hints +partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner +listen_address: 127.0.0.1 +storage_port: 7010 +rpc_port: 9170 +start_native_transport: true +native_transport_port: 9042 +column_index_size_in_kb: 4 +saved_caches_directory: target/cassandra/saved_caches +data_file_directories: + - target/cassandra/data +disk_access_mode: mmap +seed_provider: + - class_name: io.k8s.cassandra.KubernetesSeedProvider + parameters: + - seeds: "8.4.4.4,8.8.8.8" +endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch +dynamic_snitch: true +request_scheduler: org.apache.cassandra.scheduler.RoundRobinScheduler +request_scheduler_id: keyspace +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra +incremental_backups: true +concurrent_compactors: 4 +compaction_throughput_mb_per_sec: 0 +row_cache_class_name: org.apache.cassandra.cache.OHCProvider +row_cache_size_in_mb: 16 +enable_user_defined_functions: true +enable_scripted_user_defined_functions: true diff --git a/examples/cassandra/java/src/test/resources/logback-test.xml b/examples/cassandra/java/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..893b2d39108 --- /dev/null +++ b/examples/cassandra/java/src/test/resources/logback-test.xml @@ -0,0 +1,34 @@ + + + + + + + %-5level %date{HH:mm:ss,SSS} %msg%n + + + DEBUG + + + + + + + + + +