Skip to content

Commit

Permalink
Inital release
Browse files Browse the repository at this point in the history
  • Loading branch information
coltnz committed Apr 3, 2014
1 parent 203c735 commit a2acfb2
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 46 deletions.
16 changes: 13 additions & 3 deletions README.md
@@ -1,14 +1,24 @@
# ccm-clj

A Clojure library designed to ... well, that part is up to you.
A Clojure interface to Cassandra Cluster Manager (https://github.com/pcmanus/ccm) suitable for use in integration tests.

## Usage

FIXME
```clojure
(if (not (ccm/cluster? "testcluster"))
(do
(ccm/new! "testcluster" "2.0.4" 3 (get-in cfg [:cql-port]))
(ccm/cql! (io/file "./test/resources/test-keyspace.cql") nil "Keyspace")
(ccm/cql! (io/resource "schema/test-schema.cql") "mailtest" "Schema")
(ccm/cql! (io/file "./test/resources/test-data.cql") "mailtest" "Data"))
(do
(ccm/switch! "testcluster")
(ccm/start! "testcluster")))
```

## License

Copyright © 2014 FIXME
Copyright © 2014 SMX Ltd (http://smxemail.com) and Contributors.

Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
6 changes: 3 additions & 3 deletions project.clj
@@ -1,6 +1,6 @@
(defproject com.smxemail/ccm-clj "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
(defproject com.smxemail/ccm-clj "0.1.0"
:description "Clojure interface to Cassandra Cluster Manager"
:url "https://github.com/SMX-LTD/ccm-clj"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.6.0"]
Expand Down
94 changes: 64 additions & 30 deletions src/ccm_clj.clj
Expand Up @@ -11,48 +11,83 @@
;;;;;;;;;;;;;
;;; Impl

(def active-cluster (atom nil))
(def default-keyspace (atom nil))

(def ccm-dir (io/file (.getProperty (System/getProperties) "user.home") ".ccm"))

(defn- ccm [& cmd]
(defn- ccm
[& cmd]
(let [quiet (some #{:quiet} cmd)
cmd* (vec (filter #(not= :quiet %) cmd))
r (apply shell/sh "ccm" cmd*)]
(log/debug "cmd: " cmd*)
(if (and (not quiet) (not= (:out r) "") (not (.contains (:out r) "JavaLaunchHelper"))) ;java logging bug
(log/info (str "CCM=> " (str/trim (:out r)))))
(if (not= (:err r) "")
(log/error (str "CCM=> " (str/trim (:err r)))))
(if (not= (:exit r) 0)
(throw (RuntimeException. (str "CCM failure: " (str/trim (:err r)) "cmd:" cmd* ))))
(throw (RuntimeException. (str "CCM failure: " (str/trim (:err r)) " cmd:" cmd*))))
r))

(defn- conf-as-map [conf-file]
;Config parsers do not guess datatypes of values in configuration files, always storing them internally as string,
;BUT we'll keyword cos we can't help ourselves."
(apply array-map (mapcat
(fn [line]
(let [[k v] (map str/trim (re-seq #"[^:]+" line))]
(letfn [(realize [i] (condp = (.charAt i 0) ;todo pretty hacky
\[ (vec (map realize (re-seq #"[^\[\],]+" i)))
\{ (vec (map realize (re-seq #"[^\{\},]+" i)))
(str/trim i)))]
[(keyword k) (realize v)])))
(let [k (subs line 0 (inc (.indexOf line ":")))
v (subs line (inc (.indexOf line ":")))]
(letfn [(realize [i]
(as-> (str/trim i) i
(cond
(.startsWith i "[") (-> (vec (map realize (re-seq #"[^\[\],]+" i))))
(.startsWith i "{") (-> (apply array-map (map realize (re-seq #"[^\{\},:]+" i))))
(.endsWith i ":") (keyword (subs i 0 (dec (.length i))))
(= i "null") nil
:else i)))]
[(realize k) (realize v)])))
(re-seq #"[^\n]+" (slurp conf-file)))))

(defprotocol CCMCoercions
"Coerce to cqlsh args."
(as-cqlsh-arg [x] "Coerce argument to a file.")
(to-str [_] "For logging convienence"))

(extend-protocol CCMCoercions
nil
(as-cqlsh-arg [_] (throw (IllegalArgumentException. "Nil arg to cqlsh")))
(to-str [_] "")
;String ;ccm bug doenst like trailing ';' ?
;(as-cqlsh-arg [x] [(str "-x " "\"" (if (.endsWith x ";") (subs x 0 (dec (.length x))) x) "\"" " -v")])
;(to-str [x] (subs x 0 (min (.length x) 100)))

File
(as-cqlsh-arg [x] ["--file" (.getAbsolutePath x)])
(to-str [x] (.getAbsolutePath x))

URL
(as-cqlsh-arg [x] ["--file" (let [content (slurp x)
tmpFile (File/createTempFile (str x) nil)]
(spit tmpFile content)
(.getAbsolutePath tmpFile))])
(to-str [x] (.toString x))

;Reader ;ccm bug ?
;(as-cqlsh-arg [x] [(str "-x " "\"" (let [c (slurp x)] (if (.endsWith c ";") (subs c 0 (dec (.length c))) c)) "\"" " -v")])
;(to-str [x] x)
)

(defn- coerce-as-cqlsh-arg [thing]
;todo use pipe redirects
;todo use pipe redirects ?
(condp = (type thing)
File ["--file" (.getAbsolutePath thing)]
URL ["--file" (let [content (slurp thing)
tmpFile (File/createTempFile (str thing) nil)]
(spit tmpFile content)
(.getAbsolutePath tmpFile))]
; todo doesnt do compound Reader ["-x " (str "\"" (slurp thing) "\"")]
; todo doesnt do compound String ["-x " (str "\"" thing "\"")]
Reader ["-x " (str "\"" (slurp thing) "\"") "-v"]
String ["-x " (str "\"" thing "\"") "-v"]
nil (throw (IllegalArgumentException. "Nil arg to cqlsh"))
(throw (IllegalArgumentException. (str "Unknown type " (type thing) "for cqlsh arg " thing)))))
(throw (IllegalArgumentException. (str "Unknown type " (type thing) " for cql! arg " thing)))))

(declare stop!)

Expand All @@ -67,7 +102,9 @@
(defn get-active-cluster
"Get name of active cluster"
[]
@active-cluster)
(if (.exists (io/file ccm-dir "CURRENT"))
(str/trim (slurp (io/file ccm-dir "CURRENT")))
nil))

(defn set-default-keyspace!
"Set default keyspace"
Expand All @@ -82,14 +119,15 @@
(defn get-cluster-conf
"Get a map of the cluster conf"
([]
(get-cluster-conf @active-cluster))
(if (get-active-cluster) (get-cluster-conf (get-active-cluster)) nil))
([name]
(conf-as-map (io/file ccm-dir name "cluster.conf"))))

(defn get-node-conf
"Get a map of the cluster conf"
"Get a map of the node conf `name`"
([name]
(get-node-conf @active-cluster name))
{:pre (= (get-active-cluster) nil)}
(get-node-conf (get-active-cluster) name))
([cluster name]
(conf-as-map (io/file ccm-dir cluster name (str name ".conf")))))

Expand All @@ -100,25 +138,24 @@
(log/info (str name " cluster started"))
result))

(defn cqlsh!
(defn cql!
"Execute cqlsh cmd (against 'node1') in keyspace `keyspace` from cmd-source (can be File, String or URL) into active cluster."
([cmd-source]
(cqlsh! cmd-source @default-keyspace "" "node1"))
(cql! cmd-source @default-keyspace "" "node1"))
([cmd-source keyspace]
(cqlsh! cmd-source keyspace "" "node1"))
(cql! cmd-source keyspace "" "node1"))
([cmd-source keyspace log-name]
(cqlsh! cmd-source keyspace log-name "node1"))
(cql! cmd-source keyspace log-name "node1"))
([cmd-source keyspace log-name node-name]
(let [thing cmd-source
result (apply ccm (concat (if keyspace [node-name "cqlsh" "-k" keyspace] [node-name "cqlsh"]) (coerce-as-cqlsh-arg thing)))]
(log/info (str log-name " load finished (check for errors in output) : ") (subs (str thing) 0 (min (count (str thing)) 100)))
result (apply ccm (concat (if keyspace [node-name "cqlsh" "-k" keyspace] [node-name "cqlsh"]) (as-cqlsh-arg thing)))]
(log/info (str log-name " load finished of " (to-str cmd-source) " (check for errors in output)"))
result)))

(defn switch!
"Switch active cluster to `name`"
[name]
(let [result (ccm "switch" name)]
(reset! active-cluster name)
(log/info (str "Switch active cluster to " name))
result))

Expand All @@ -131,20 +168,17 @@
"Stop the current CCM cluster."
[]
(let [result (ccm "stop")]
(reset! active-cluster nil)
(log/info (str @active-cluster " cluster stopped"))
(log/info (str (get-active-cluster) " cluster stopped"))
result))

(defn remove!
"Remove cluster `name` from CCM including all data."
[name]
(let [result (ccm "remove" name)]
(if (= name @active-cluster)
(reset! active-cluster nil))
(log/info (str name " cluster removed"))
result))

(defn has-cluster?
(defn cluster?
"Is `name` found in list of CCM clusters."
[name]
;Correct by CCM but do we go further?
Expand All @@ -157,7 +191,7 @@
[name version num-nodes cql-port]
(let [cluster-dir (io/file ccm-dir name)
cluster-exists (.exists cluster-dir)]
(if (not (has-cluster? `name))
(if (not (cluster? `name))
(do
(log/info "Creating new cluster" name)
(ccm "create" name "-v" version)
Expand Down
26 changes: 18 additions & 8 deletions test/ccm_clj_test.clj
Expand Up @@ -4,24 +4,34 @@
[ccm-clj :refer :all]))

(def existing (get-clusters))
(def current (get-active-cluster))

(defn tidy-up {:expectations-options :after-run} []
(ccm-clj/remove! "ccmcljtest1"))
(ccm-clj/remove! "ccmcljtest1")
(if current (switch! current)))

;todo check :err expect-no-err ?
(expect (new! "ccmcljtest1" "2.0.4" 3 19111))
(expect (cqlsh! (io/file "./test/resources/test-keyspace.cql"))) ;cql as url
(expect (cqlsh! (io/resource "test-schema.cql"))) ;cql as string, keyspace in file
(expect (cqlsh! (io/file "./test/resources/test-data.cql") "ccmclj")) ;with given keyspace

;cql as file
(expect (cql! (io/file "./test/resources/test-keyspace.cql")))
;cql as url, keyspace in file
(expect (cql! (io/resource "test-schema.cql")))
;file with given keyspace
(expect (cql! (io/file "./test/resources/test-data.cql") "ccmclj"))

(expect (set-default-keyspace! "ccmclj"))
(expect (cqlsh! (io/file "./test/resources/test-data2.cql"))) ;using default keyspace
;load string using default keyspace - and trim surplus ; which ccm spews on ;todo still doesnt work?
;(expect (cql! "update testtable set data = '22' where id = 2; insert into testtable (id, data) values (3, '2')"));
;(expect (slurp (io/file "./test/resources/test-data2.table")) (cql! "select * from testtable"))

(expect (set ["node1" "node2" "node3"]) (set (:nodes (get-cluster-conf))))

(expect "ccmcljtest1" (get-active-cluster))
(expect (conj (set existing) "ccmcljtest1") (set (get-clusters)))
(expect (has-cluster? "ccmcljtest1"))
(expect (cluster? "ccmcljtest1"))
(expect "ccmclj" (get-default-keyspace))
(expect (not (has-cluster? "ccmcljtest2")))
(expect (not (cluster? "ccmcljtest2")))

(expect (new! "ccmcljtest2" "1.2.0" 1 19212))
(expect "ccmcljtest2" (get-active-cluster))
Expand All @@ -36,7 +46,7 @@

(expect (exec! "add" "-r 0" "-t 127.0.0.4:19112" "-j 19114" "-l 127.0.0.4:19114" "--binary-itf=127.0.0.4:19111" "node4"))
(expect (hash-set "node1" "node2" "node3" "node4") (set (:nodes (get-cluster-conf))))

(expect (exec! "node4" "remove"))
(expect (hash-set "node1" "node2" "node3") (set (:nodes (get-cluster-conf))))


2 changes: 0 additions & 2 deletions test/resources/test-data2.cql

This file was deleted.

7 changes: 7 additions & 0 deletions test/resources/test-data2.table
@@ -0,0 +1,7 @@
smxnz:32 | 20130101 | sid3 | 20130101 | msg-id-1 | null | null | null | fred@hotmail.com | null | null | subj12
smxnz:32 | 20130101 | sid6 | 20130101 | msg-id-2 | null | null | null | bob@hotmail.com | null | null | subj3
smxnz:32 | 20140101 | sid4 | 20130101 | msg-id-1 | null | null | null | fred@hotmail.com | null | null | subj12
smxnz:33 | 20130201 | sid9 | 20130201 | msg-id-1 | null | null | null | fred@hotmail.com | null | null | subj1
smxnz:33 | 20130101 | sid2 | 20130101 | msg-id-3 | null | null | null | bob@hotmail.com | null | null | subj1
smxnz:33 | 20130101 | sid5 | 20130101 | msg-id-1 | null | null | null | fred@hotmail.com | null | null | subj1
smxnz:33 | 20130101 | sid7 | 20130101 | msg-id-1 | null | null | null | fred@hotmail.com | null | null | subj1

0 comments on commit a2acfb2

Please sign in to comment.