Skip to content

Commit

Permalink
Add a RobustIRC test.
Browse files Browse the repository at this point in the history
See http://robustirc.net/ for more information, specifically:

http://robustirc.net/docs/adminguide.html for setup/overview
http://robustirc.net/docs/robustsession.html for the protocol
  • Loading branch information
stapelberg committed Oct 6, 2015
1 parent 7b3fbbb commit 9b0ae18
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 0 deletions.
13 changes: 13 additions & 0 deletions robustirc/project.clj
@@ -0,0 +1,13 @@
(defproject jepsen.robustirc "0.1.0"
:description "Jepsen tests for RobustIRC"
:url "https://github.com/aphyr/jepsen"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.6.0"]
[jepsen "0.0.5"]
[clj-http "2.0.0"]
[digest "1.4.4"]
[cheshire "5.5.0"]
[org.clojure/clojure "1.6.0"]
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
])
19 changes: 19 additions & 0 deletions robustirc/resources/cert.pem
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDJjCCAg6gAwIBAgIQLezNyzLAh3x8vDkmkXp1xjANBgkqhkiG9w0BAQsFADAd
MRswGQYDVQQKExJSb2J1c3RJUkMgbG9jYWxuZXQwHhcNMTUxMDA0MTgwMzE1WhcN
MjUxMDAxMTgwMzE1WjAdMRswGQYDVQQKExJSb2J1c3RJUkMgbG9jYWxuZXQwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYSluq4S/nveSin3q8pyZNBzQY
KB+6LFN0VgPzY0QjakK9rjLg/YEH4vi32n3je+ONhsZEl/pYBP0tgO3dyEWwl6WE
BuVJ/jA8/BQ/E7sZVKNr+Pf89x6cMz4v22dEiR9yUCLJ+zkRg8x+zSxFjP/nGeDp
hsuGdYZZC4ekzKGRRhHIJT3kNx9UTWob7+godAJu1T+s8zb642my5bPBxljd9+Eb
o89CkrfEszI3uy3J9xmiS+Snl89m/ye7PXUvLO2FCXmG/FsEOGf0NoqVWpIS73NQ
cb50hdjdokaYwzvOlv2KwlwymWdcUJffUBcvrw/sbD5HZwr3+re0M+CvL9exAgMB
AAGjYjBgMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNV
HRMBAf8EBTADAQH/MCgGA1UdEQQhMB+CCWxvY2FsaG9zdIICbjGCAm4yggJuM4IC
bjSCAm41MA0GCSqGSIb3DQEBCwUAA4IBAQAX7siP/O0+Tk5Fv/PKH2YDt3mdW1+V
+2rtFBfxNKi5J74ifECcXJ6S52IrM9mVHh+0v4ElKfEAU66ADbvawn8jEi4RR/lN
RFI+RljlZOX14EhGPTY8iC8Xd+321OUPH1SILwp7GlH8jrp+mD6oURt2XyX7/lvU
YeKYbK3C+CjA0VZOkIBk2tPfbfShZXEboQbrZgvhwQOOrb2iWl2KqbsiYgrOyEPx
O83WH7AWDBFwjZJa0X39HLoelP0Y7PyaOi7XMQ99kXdtloUVYY8fL6utXFq4XlU0
X0LV63lZWvNxDo3KafepxrEp2EexuycCgzP02CBLGG9ju3oDnTMGphAV
-----END CERTIFICATE-----
68 changes: 68 additions & 0 deletions robustirc/resources/gencert.go
@@ -0,0 +1,68 @@
package main

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"os"
"path/filepath"
"time"
)

func main() {
dir := "/tmp/"
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Panicf("failed to generate private key: %s", err)
}

notBefore := time.Now()
notAfter := notBefore.Add(10 * 365 * 24 * time.Hour)

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Panicf("failed to generate serial number: %s", err)
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"RobustIRC localnet"},
},
DNSNames: []string{"localhost", "n1", "n2", "n3", "n4", "n5"},
NotBefore: notBefore,
NotAfter: notAfter,

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
log.Panicf("Failed to create certificate: %s", err)
}

certOut, err := os.Create(filepath.Join(dir, "cert.pem"))
if err != nil {
log.Panicf("failed to open cert.pem for writing: %s", err)
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
log.Print("written cert.pem\n")

keyOut, err := os.OpenFile(filepath.Join(dir, "key.pem"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Print("failed to open key.pem for writing:", err)
return
}
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
keyOut.Close()
log.Print("written key.pem\n")
}
27 changes: 27 additions & 0 deletions robustirc/resources/key.pem
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA2EpbquEv573kop96vKcmTQc0GCgfuixTdFYD82NEI2pCva4y
4P2BB+L4t9p943vjjYbGRJf6WAT9LYDt3chFsJelhAblSf4wPPwUPxO7GVSja/j3
/PcenDM+L9tnRIkfclAiyfs5EYPMfs0sRYz/5xng6YbLhnWGWQuHpMyhkUYRyCU9
5DcfVE1qG+/oKHQCbtU/rPM2+uNpsuWzwcZY3ffhG6PPQpK3xLMyN7styfcZokvk
p5fPZv8nuz11LyzthQl5hvxbBDhn9DaKlVqSEu9zUHG+dIXY3aJGmMM7zpb9isJc
MplnXFCX31AXL68P7Gw+R2cK9/q3tDPgry/XsQIDAQABAoIBACFwPYPJlLJrNTG/
HiaUYON/vZoOefk6aRyPP8UdD6e6Ad95UvxdtNkXSgSCjvvgZ2m18GkiZd29x7aF
PEEReVS33SrwNfqeha6n+ilWT5K29xz2PMCrI9xaP77+WJTL7kSqBvbbFBOVIMRp
y/JkvhtocNEpucVr3f1ePvZHfKMMvWga/UxjafbAouduIDqoXLfSTzYhwqr+uTmO
dTCUd5r47YAUJqM+ifb3j1LcefEgDh1NJyN3Spypayx47cMH7X9bOh3BjAqKW7XY
JB6pcSj67EvaDHJPMwUspOHz3iRH1YWP7qnuI+mYwnMs8hXdI+m/7g03qIrf0mfe
7sru0dECgYEA6Te/LQ6wEAUBQxGtvcOdr6bJag7E85+7ONGbuzwgUmTI5ll7fwmL
551OuKAwQpQeDO+31p/zsUI0/Epj7kUTCdJ7rUVX9jYfIkG2zgmjLbqs4odps13T
DaOK2lzEE/ryhJ6Wn/xjMpyvufIdMtGs3b1+oYayr1d7mDlZqb7pHj8CgYEA7WtM
KEwPVpuJW0obKaqoqEdQFBDD0grGQKOlkXWJhR+GSd4ca4adkqrxGdJEWr8lX6Nv
3LGsdCbpTVnnuInx2p6gJ5hdgEr0oSgkukBh/uoYl0jSEQrh9ULKqWoqNRAHrG5O
mOSHuD3xng31nT8z+gTJo0sFRGs5wmfrVBl4bg8CgYAxrI0MxM8damN9bJuMCslg
a28FJwYuOfx/uq2n8MOVqR/daHvUcC0bdTcaPgxpcfEAomKO1VzriYWNoy6rxoyq
j+mF23pD1/1Dp6mPMyBNkjBWw67w5HAavJXLsuyUsOPM+ZZtly1tz0/ilD5B38Up
Kb56Gx2eGU0a/EQr0p0GswKBgQC29LNobkx3mj48GhxafIehveqou86bIvBpyXIC
aMgDzSpH7CCMf90XtCE9m7pPD/O2ApVmRYi2rXGlyP13SN4WcZzLRm/vOFPRiMTc
vlaGAVHX6ybuOSutO74+1XAXGfY/23U3/wvTX/C8stcuSRE9vsi/zEUP121YMYq4
cTRL4wKBgAtFfumGuptom+ikYlgswMURkJY2LwhFnLF5ITgwf0UezGxdcGvkYGrq
LRx94Tq/wBogLq9AvMl7kS0WcFQ2Ce3y04gFGSFBa0/yRb5EtSSSuHgfkst5E20x
JIXDAAb8GDqhUvOvWtbXPGlNqCmWLJCmD9LfAitHdkCyiIeSM9Tr
-----END RSA PRIVATE KEY-----
217 changes: 217 additions & 0 deletions robustirc/src/jepsen/robustirc.clj
@@ -0,0 +1,217 @@
(ns jepsen.robustirc
(:require
[clojure.tools.logging :refer [debug info warn]]
[clojure.java.io :as io]
[clojure.string :as str]
[clojure.core.async :as async]
[clj-http.client :as httpclient]
[digest :as digest]
[jepsen
[client :as client]
[core :as jepsen]
[db :as db]
[tests :as tests]
[control :as c :refer [|]]
[checker :as checker]
[nemesis :as nemesis]
[generator :as gen]]
[jepsen.os.debian :as debian]
[cheshire.core :as json])
(:import
[java.net URL]))

(defn db []
"RobustIRC."
(reify db/DB
(setup! [this test node]
(c/su
(c/ssh* {:cmd "killall robustirc"})
(try (c/exec :dpkg-query :-l :golang-go)
(catch RuntimeException _
(info "Installing golang-go")
(c/exec :apt-get :install :-y :golang-go)))
(try (c/exec :dpkg-query :-l :mercurial)
(catch RuntimeException _
(info "Installing mercurial")
(c/exec :apt-get :install :-y :mercurial)))
(c/exec :env "GOPATH=~/gocode"
:go :get :-u "github.com/robustirc/robustirc")

; generated by resources/gencert.go
(c/upload (.getFile (io/resource "cert.pem")) "/tmp/cert.pem")
(c/upload (.getFile (io/resource "key.pem")) "/tmp/key.pem")
(c/ssh* {:cmd "rm -rf /var/lib/robustirc"})
(c/ssh* {:cmd "mkdir -p /var/lib/robustirc"})
(jepsen/synchronize test)

(let [cmd (str
"/sbin/start-stop-daemon --start --background --exec ~/gocode/bin/robustirc --"
" -listen=" (name node) ":13001"
" -network_password=secret"
" -network_name=jepsen"
" -tls_cert_path=/tmp/cert.pem"
" -tls_ca_file=/tmp/cert.pem"
" -tls_key_path=/tmp/key.pem"
" -singlenode")]
(if-not (= node (jepsen/primary test))
(Thread/sleep 1000)
(do
(info node (str "running: " cmd))
(c/ssh* {:cmd cmd})
(Thread/sleep 5000))))
(jepsen/synchronize test)
(let [cmd (str
"/sbin/start-stop-daemon --start --background --exec ~/gocode/bin/robustirc --"
" -listen=" (name node) ":13001"
" -network_password=secret"
" -network_name=jepsen"
" -tls_cert_path=/tmp/cert.pem"
" -tls_ca_file=/tmp/cert.pem"
" -tls_key_path=/tmp/key.pem"
" -join=" (name (jepsen/primary test)) ":13001")]
(if (= node (jepsen/primary test))
(Thread/sleep 100)
(do
(info node (str "running: " cmd))
(c/ssh* {:cmd cmd})
(Thread/sleep 5000))))
(jepsen/synchronize test)
(info node "setup done")))

(teardown! [this test node]
(c/su
(c/ssh* {:cmd "killall robustirc"}))
)))

(defn with-nemesis
"Wraps a client generator in a nemesis that induces failures and eventually
stops."
[client]
(gen/phases
(gen/phases
(->> client
(gen/nemesis
(gen/seq (cycle [(gen/sleep 0)
{:type :info, :f :start}
(gen/sleep 10)
{:type :info, :f :stop}])))
(gen/time-limit 30))
(gen/nemesis (gen/once {:type :info, :f :stop}))
(gen/sleep 5))))

(defn create-session
[node]
(assoc (json/parse-string
(get
(httpclient/post (str "https://" (name node) ":13001/robustirc/v1/session")
{:insecure? true})
:body))
:node node))

(defn post-message
[session ircmessage]
(let [msgid (bit-or (rand-int Integer/MAX_VALUE)
(Long/parseLong (subs (digest/md5 ircmessage) 17) 16))]
(httpclient/post
(str "https://" (name (get session :node)) ":13001/robustirc/v1/" (get session "Sessionid") "/message")
{:headers {"X-Session-Auth" (get session "Sessionauth")}
:insecure? true
:content-type :json
:form-params {:Data ircmessage
:ClientMessageId msgid}})))

(defn read-all
[session timeoutmsec]
(let [out (atom [])]
(jepsen.util/timeout
timeoutmsec
@out
(doseq [msg (json/parsed-seq (io/reader (get (httpclient/get
(str "https://" (name (get session :node)) ":13001/robustirc/v1/" (get session "Sessionid") "/messages")
{:headers {"X-Session-Auth" (get session "Sessionauth")}
:query-params {:lastseen "0.0"}
:insecure? true
:as :stream}) :body)))]
(swap! out conj msg)))))

; XXX: use a proper IRC parser for filter-topic and extract-topic
(defn filter-topic
[msg]
(let [s (str/split (get msg "Data") #" ")]
(and
(> (count s) 1)
(= (nth s 1) "TOPIC"))))

(defn extract-topic
[msg]
(let [s (str/split (get msg "Data") #":")]
(Integer/parseInt (nth s (- (count s) 1)))))

(defrecord SetClient [node session]
client/Client

(setup! [this test node]
(info node "creating robustsession")
(let [session (create-session node)]
(info node (str "session " (get session "Sessionid")))
(post-message session (str "NICK " (name node)))
(post-message session "USER j j j j")
(post-message session "JOIN #jepsen")
(assoc this :node node
:session session)))

(invoke! [this test op]
(try
(case (:f op)
:add (try
(do
(post-message (-> session) (str "TOPIC #jepsen :" (:value op)))
(assoc op :type :ok))
(catch Exception e
(assoc op :type :fail :value :node-failure)))
:read (let [msgs (read-all session 1000)
filtered (filter filter-topic msgs)
converted (map extract-topic filtered)]
(assoc op
:type :ok
:value (set converted))))))

(teardown! [this test]
(info node "teardown code goes here")))

(defn set-client
[node]
(SetClient. nil nil))

(defn basic-test
[opts]
(merge tests/noop-test
{:name (str "robustirc " (:name opts))
:os debian/os
:db (db)
:nemesis (nemesis/partition-random-halves)}
(dissoc opts :name :version)))

(defn sets-test
[version]
(basic-test
{:name "set"
:version version
:client (set-client nil)
:generator (gen/phases
(->> (range)
(map (partial array-map
:type :invoke
:f :add
:value))
gen/seq
(gen/delay 1/10)
with-nemesis)
(->> {:type :invoke, :f :read, :value nil}
gen/once
gen/clients))
:checker (checker/compose
{:perf (checker/perf)
:set checker/set})}))


9 changes: 9 additions & 0 deletions robustirc/test/jepsen/robustirc_test.clj
@@ -0,0 +1,9 @@
(ns jepsen.robustirc-test
(:require [clojure.test :refer :all]
[jepsen.core :refer [run!]]
[jepsen.robustirc :refer :all]))

(def version "0.1")

(deftest sets-test'
(is (:valid? (:results (run! (sets-test version))))))

0 comments on commit 9b0ae18

Please sign in to comment.