Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

barebone

  • Loading branch information...
commit a6e3d568769d7a8bce3db6d499f511dabbaab9c2 0 parents
Jason Jackson authored
27 Capfile
@@ -0,0 +1,27 @@
+# Docs at http://confluence.local.twitter.com/display/RELEASE/Twitter-cap-utils+README
+begin
+ require 'rubygems'
+ require "bundler/setup"
+ require "railsless-deploy"
+ require 'twitter_cap_utils'
+rescue LoadError => e
+ puts e.message
+ abort "Please gem install twitter-cap-utils railsless-deploy"
+end
+
+set :user, :twitter
+set :application, "kestrelthrift"
+set :repository, "http://git.local.twitter.com/ro/#{application}"
+
+task :staging do
+ role :app, "server1", "server2", "etc"
+end
+
+task :canary do
+ role :app, "server1"
+end
+
+task :production do
+ role :app, "server1", "server2", "etc"
+end
+
8 Gemfile
@@ -0,0 +1,8 @@
+# use "bundle install" to update gems; "gem install bundler" to install bundler.
+source :rubygems
+source "http://gems.local.twitter.com"
+gem "thrift_client", "0.6.2"
+gem "thrift", "0.6"
+gem "railsless-deploy"
+gem "capistrano"
+gem "twitter-cap-utils", "~>0.8.0"
24 README.md
@@ -0,0 +1,24 @@
+# Project Kestrelthrift
+
+Welcome to your kestrelthrift project! To make sure things are working
+properly, you may want to:
+
+ $ sbt update test
+
+There is a tutorial for what to do next, which you can find in the
+scala-bootstrapper README.rdoc file.
+
+# Configuring Intellij
+
+If you want to setup Intellij, it has to happen off to the side:
+
+ $ sbt
+ > *sbtIdeaRepo at http://mpeltonen.github.com/maven/
+ > *idea is com.github.mpeltonen sbt-idea-processor 0.4.0
+ > update
+ > idea
+
+# Documenting your project
+
+Add documentation here! Eventually, you'll be able to publish this to
+a web site for the world to easily find and read.
187 TUTORIAL.md
@@ -0,0 +1,187 @@
+# Welcome to Kestrelthrift!
+
+## Setup
+
+Scala-bootstrapper has created a fully-functional Scala service for
+you. You can verify that things are set up correctly by doing:
+
+ $ sbt update test
+
+## Tutorial
+
+### Run your service!
+
+There are two ways to start your service. You can build a runnable
+jar and tell java to run it directly:
+
+ $ sbt package-dist
+ $ java -Dstage=development -jar ./dist/kestrelthrift/kestrelthrift-1.0.0-SNAPSHOT.jar
+
+or you can ask sbt to run your service:
+
+ $ sbt 'run -f config/development.scala'
+
+### Verify that the service is running
+
+The java/sbt command-lines will "hang" because the server is running in the
+foreground. (In production, we use libslack-daemon to wrap java processes into
+unix daemons.) Go to another terminal and check for a logfile. If your server
+is named "kestrelthrift", there should be a `kestrelthrift.log` with contents like this:
+
+ INF [20110615-14:05:41.656] stats: Starting JsonStatsLogger
+ INF [20110615-14:05:41.674] admin: Starting TimeSeriesCollector
+ DEB [20110615-14:05:41.792] nio: Using the autodetected NIO constraint level: 0
+
+That's your indication that the server is running. :)
+
+### View the Thrift IDL for your service
+
+The IDL for your service is in `src/main/thrift/kestrelthrift.thrift`. The
+Thrift compiler uses the IDL to generate bindings for various
+languages, making it easy for scripts in those languages to talk to
+your service. More information about Thrift and how to write an IDL
+for your service can be found [here](http://wiki.apache.org/thrift/Tutorial).
+
+### Call your service from ruby
+
+Your service implements simple get() and put() methods. Once you have
+your server running, as above, bring up a different shell and:
+
+ $ cd kestrelthrift
+ $ bundle install
+ $ ./dist/kestrelthrift/scripts/console
+ >> $client
+ >> $client.put("key1", "valueForKey")
+ >> $client.get("key1")
+
+### Look at the stats for your service
+
+By default, your project is configured to use
+[Ostrich](https://github.com/twitter/ostrich), a library for service
+configuration, administration, and stats reporting. Your config file
+in `config/development.scala` defines which port ostrich uses for admin
+requests. You can view the stats via that port:
+
+ $ curl localhost:9900/stats.txt
+ counters:
+ Kestrelthrift/connects: 1
+ Kestrelthrift/requests: 2
+ Kestrelthrift/success: 2
+ ...
+
+Ostrich also stores historial stats data and can build
+[graphs](http://localhost:9900/graph/) for you.
+
+### Stop the service
+
+You can ask the server to shutdown over the admin port also:
+
+ $ curl localhost:9900/shutdown.txt
+ ok
+
+### View the implementation of get() and put()
+
+In `src/main/scala`, take a look at `KestrelthriftServiceImpl.scala`. (This may
+have a different name, based on what you called your server.)
+
+The base interface is specified by thrift. Additionally, we're using Twitter's
+async I/O framework: finagle. Finagle (and a lot of great documentation about
+it) is hosted here: https://github.com/twitter/finagle
+
+### Try adding some timers and counters
+
+At the top of KestrelthriftServiceImpl.scala, add:
+
+ import com.twitter.ostrich.stats.Stats
+
+Then inside get():
+
+ Stats.incr("kestrelthrift.gets")
+
+and inside put():
+
+ Stats.incr("kestrelthrift.puts")
+
+Then restart your server, talk to the server via console, and check
+your stats:
+
+ $ curl localhost:9900/stats.txt
+ counters:
+ Kestrelthrift/connects: 1
+ Kestrelthrift/requests: 2
+ Kestrelthrift/success: 2
+ kestrelthrift.gets: 1
+ kestrelthrift.puts: 1
+
+You can also time various things that your server is doing, for
+example:
+
+ Stats.time("kestrelthrift.put.latency") {
+ Thread.sleep(10) // so you can see it
+ database(key) = value
+ }
+
+### Specs: let's add some tests
+
+[Specs](http://code.google.com/p/specs/) is a Behavior-Driven Design
+framework that allows you to write semi-human-readable descriptions of
+how your service should behave and test that those descriptions are
+valid. You already have some Specs code for your project in
+src/test/scala/com/twitter/kestrelthrift/KestrelthriftServiceSpec.scala. Check
+out the existing test and add a new one for the counter functionality
+we just added.
+
+ import com.twitter.ostrich.stats.Stats
+
+ ...
+
+ "verify stats" in {
+ val counters = Stats.getCounters
+ foofa.put("name", "bluebird")()
+ foofa.get("name")() mustEqual "bluebird"
+ counters.getOrElse("foofa.gets", 1) must_==1
+ counters.getOrElse("foofa.puts", 1) must_==1
+ }
+
+TODO: add link to scala school lesson on Specs
+
+### Automatically compile and test your server when you change code
+
+By now you've had to Ctrl-C your server and restart it to get changes
+to show up. This gets a little tiresome. The build tool we are
+using,
+[SBT (simple build tool)](http://code.google.com/p/simple-build-tool/)
+has a console that you can access by just running "sbt" from the
+command line.
+
+ $ sbt
+ [info] Standard project rules 0.11.4 loaded (2011-03-18).
+ [warn] No .svnrepo file; no svn repo will be configured.
+ [info] Building project kestrelthrift 1.0.0-SNAPSHOT against Scala 2.8.1
+ [info] using KestrelthriftProject with sbt 0.7.4 and Scala 2.7.7
+
+SBT has a wide array of features, but a useful one right now is to
+use the "~ test" command.
+
+ > ~ test
+
+The tilde tells SBT to look for changes to your source files and
+re-execute the command when it detects a change.
+
+TODO: add link to scala school lesson on SBT
+
+### Add an admin / dashboard page.
+
+### Add a new dependency to your project, perhaps twitter/util?
+
+### Take a tour of the logs our service is producing.
+
+### Add command-line parameters for your service.
+-D foo=bar
+runtime.arguments.get("foo")
+
+### Storage: let's persist the data in Cassandra!
+
+### Twitter API: let's listen to the Firehose!
+
+### Twitter API: let's fetch some statuses & users & stuff.
41 config/development.scala
@@ -0,0 +1,41 @@
+import com.twitter.conversions.time._
+import com.twitter.logging.config._
+import com.twitter.ostrich.admin.config._
+import com.twitter.kestrelthrift.config._
+
+// development mode.
+new KestrelthriftServiceConfig {
+
+ // Add your own config here
+
+ // Where your service will be exposed.
+ thriftPort = 9999
+
+ // Ostrich http admin port. Curl this for stats, etc
+ admin.httpPort = 9900
+
+ // End user configuration
+
+ // Expert-only: Ostrich stats and logger configuration.
+
+ admin.statsNodes = new StatsConfig {
+ reporters = new TimeSeriesCollectorConfig
+ }
+
+ loggers =
+ new LoggerConfig {
+ level = Level.DEBUG
+ handlers = new FileHandlerConfig {
+ filename = "kestrelthrift.log"
+ roll = Policy.SigHup
+ }
+ } :: new LoggerConfig {
+ node = "stats"
+ level = Level.INFO
+ useParents = false
+ handlers = new FileHandlerConfig {
+ filename = "stats.log"
+ formatter = BareFormatterConfig
+ }
+ }
+}
41 config/production.scala
@@ -0,0 +1,41 @@
+import com.twitter.conversions.time._
+import com.twitter.logging.config._
+import com.twitter.ostrich.admin.config._
+import com.twitter.kestrelthrift.config._
+
+// production mode.
+new KestrelthriftServiceConfig {
+
+ // Add your own config here
+
+ // Where your service will be exposed.
+ thriftPort = 9999
+
+ // Ostrich http admin port. Curl this for stats, etc
+ admin.httpPort = 9900
+
+ // End user configuration
+
+ // Expert-only: Ostrich stats and logger configuration.
+
+ admin.statsNodes = new StatsConfig {
+ reporters = new TimeSeriesCollectorConfig
+ }
+
+ loggers =
+ new LoggerConfig {
+ level = Level.INFO
+ handlers = new FileHandlerConfig {
+ filename = "/var/log/kestrelthrift/production.log"
+ roll = Policy.SigHup
+ }
+ } :: new LoggerConfig {
+ node = "stats"
+ level = Level.INFO
+ useParents = false
+ handlers = new FileHandlerConfig {
+ filename = "stats.log"
+ formatter = BareFormatterConfig
+ }
+ }
+}
41 config/staging.scala
@@ -0,0 +1,41 @@
+import com.twitter.ostrich.admin.config._
+import com.twitter.conversions.time._
+import com.twitter.logging.config._
+import com.twitter.kestrelthrift.config._
+
+// staging mode.
+new KestrelthriftServiceConfig {
+
+ // Add your own config here
+
+ // Where your service will be exposed.
+ thriftPort = 9999
+
+ // Ostrich http admin port. Curl this for stats, etc
+ admin.httpPort = 9900
+
+ // End user configuration
+
+ // Expert-only: Ostrich stats and logger configuration.
+
+ admin.statsNodes = new StatsConfig {
+ reporters = new TimeSeriesCollectorConfig
+ }
+
+ loggers =
+ new LoggerConfig {
+ level = Level.INFO
+ handlers = new FileHandlerConfig {
+ filename = "/var/log/kestrelthrift/production.log"
+ roll = Policy.SigHup
+ }
+ } :: new LoggerConfig {
+ node = "stats"
+ level = Level.INFO
+ useParents = false
+ handlers = new FileHandlerConfig {
+ filename = "stats.log"
+ formatter = BareFormatterConfig
+ }
+ }
+}
26 config/test.scala
@@ -0,0 +1,26 @@
+import com.twitter.conversions.time._
+import com.twitter.logging.config._
+import com.twitter.ostrich.admin.config._
+import com.twitter.kestrelthrift.config._
+
+// test mode.
+new KestrelthriftServiceConfig {
+
+ // Add your own config here
+
+ // Where your service will be exposed.
+ thriftPort = 9999
+
+ // Ostrich http admin port. Curl this for stats, etc
+ admin.httpPort = 9900
+
+ // End user configuration
+
+ // Expert-only: Ostrich stats and logger configuration.
+
+ loggers =
+ new LoggerConfig {
+ level = Level.INFO
+ handlers = new ConsoleHandlerConfig
+ }
+}
8 project/build.properties
@@ -0,0 +1,8 @@
+#Project properties
+#Thu Feb 24 16:45:35 PST 2011
+project.organization=com.twitter
+project.name=kestrelthrift
+sbt.version=0.7.4
+project.version=1.0.0-SNAPSHOT
+build.scala.versions=2.8.1
+project.initialize=false
48 project/build/KestrelthriftProject.scala
@@ -0,0 +1,48 @@
+import sbt._
+import Process._
+import com.twitter.sbt._
+
+/**
+ * Sbt project files are written in a DSL in scala.
+ *
+ * The % operator is just turning strings into maven dependency declarations, so lines like
+ * val example = "com.example" % "exampleland" % "1.0.3"
+ * mean to add a dependency on exampleland version 1.0.3 from provider "com.example".
+ */
+class KestrelthriftProject(info: ProjectInfo) extends StandardServiceProject(info)
+ with CompileThriftScala
+ with NoisyDependencies
+ with DefaultRepos
+ with SubversionPublisher
+ with PublishSourcesAndJavadocs
+ with PublishSite
+{
+ val finagleVersion = "1.2.5"
+
+ val finagleC = "com.twitter" % "finagle-core" % finagleVersion
+ val finagleT = "com.twitter" % "finagle-thrift" % finagleVersion
+ val finagleO = "com.twitter" % "finagle-ostrich4" % finagleVersion
+
+ // thrift
+ val libthrift = "thrift" % "libthrift" % "0.5.0"
+ val util = "com.twitter" % "util" % "1.8.3"
+
+ override def originalThriftNamespaces = Map("Kestrelthrift" -> "com.twitter.kestrelthrift.thrift")
+ override val scalaThriftTargetNamespace = "com.twitter.kestrelthrift"
+
+ val slf4jVersion = "1.5.11"
+ val slf4jApi = "org.slf4j" % "slf4j-api" % slf4jVersion withSources() intransitive()
+ val slf4jBindings = "org.slf4j" % "slf4j-jdk14" % slf4jVersion withSources() intransitive()
+
+ // for tests
+ val specs = "org.scala-tools.testing" % "specs_2.8.1" % "1.6.7" % "test" withSources()
+ val jmock = "org.jmock" % "jmock" % "2.4.0" % "test"
+ val hamcrest_all = "org.hamcrest" % "hamcrest-all" % "1.1" % "test"
+ val cglib = "cglib" % "cglib" % "2.1_3" % "test"
+ val asm = "asm" % "asm" % "1.5.3" % "test"
+ val objenesis = "org.objenesis" % "objenesis" % "1.1" % "test"
+
+ override def mainClass = Some("com.twitter.kestrelthrift.Main")
+
+ override def subversionRepository = Some("http://svn.local.twitter.com/maven")
+}
24 project/plugins/Plugins.scala
@@ -0,0 +1,24 @@
+import sbt._
+
+class Plugins(info: ProjectInfo) extends PluginDefinition(info) {
+ import scala.collection.jcl
+ val environment = jcl.Map(System.getenv())
+ def isSBTOpenTwitter = environment.get("SBT_OPEN_TWITTER").isDefined
+ def isSBTTwitter = environment.get("SBT_TWITTER").isDefined
+
+ override def repositories = if (isSBTOpenTwitter) {
+ Set("twitter.artifactory" at "http://artifactory.local.twitter.com/open-source/")
+ } else if (isSBTTwitter) {
+ Set("twitter.artifactory" at "http://artifactory.local.twitter.com/repo/")
+ } else {
+ super.repositories ++ Set(
+ "twitter.com" at "http://maven.twttr.com/",
+ "scala-tools" at "http://scala-tools.org/repo-releases/",
+ "freemarker" at "http://freemarker.sourceforge.net/maven2/"
+ )
+ }
+ override def ivyRepositories = Seq(Resolver.defaultLocal(None)) ++ repositories
+
+ val standardProject = "com.twitter" % "standard-project" % "0.12.7"
+ val sbtThrift = "com.twitter" % "sbt-thrift" % "1.4.4"
+}
125 run
@@ -0,0 +1,125 @@
+#!/bin/bash
+
+function path_append {
+ _var=$1
+ _path=$2
+ eval "
+ if [[ -z \"\$$_var\" ]] ; then
+ export $_var=$_path
+ elif ! echo \$$_var | egrep -q \"(^|:)$_path($|:)\" ; then
+ export $_var=\$$_var:$_path
+ fi"
+}
+
+function path_prepend() {
+ _var=$1
+ _path=$2
+ eval "
+ if [[ -z \"\$$_var\" ]] ; then
+ export $_var=$_path
+ elif ! echo \$$_var | egrep -q \"(^|:)$_path($|:)\" ; then
+ export $_var=$_path:\$$_var
+ fi"
+}
+
+function find_sbt_root {
+ while [ ! -d project -a "x$PWD" != "x/" ] ; do
+ cd ..
+ done
+
+ if [ "x$PWD" = "/" ]; then
+ echo "couldn't find sbt project!" 1>&2
+ exit 1
+ fi
+
+ echo $PWD
+}
+
+function ensure_java_bin_on_path {
+ if [ -d "$JAVA_HOME" ]; then
+ __java_bindir="$JAVA_HOME/bin"
+ else
+ __java_bindir=`which java | xargs readlink | xargs dirname`
+ fi
+
+ if [ -x "$__java_bindir/java" ]; then
+ path_append PATH $__java_bindir
+ else
+ echo "Binary 'java' is not on the PATH, and JAVA_HOME is not set. Fix one of these."
+ exit 1
+ fi
+}
+
+function include {
+ _var=$1
+ _dir=$2
+
+ for jar in $_dir/lib_managed/compile/*.jar; do
+ path_append $_var $jar
+ done
+
+ for jar in $_dir/lib/*.jar; do
+ path_append $_var $jar
+ done
+
+ path_append $_var $_dir/src/main/resources
+ path_append $_var $_dir/target/classes
+}
+
+ensure_java_bin_on_path
+
+root=$(find_sbt_root)
+if [ $? -ne 0 ]; then
+ exit 1
+fi
+
+if [ -z $project ]; then
+ project="."
+fi
+
+set -- $(getopt i:ygdh: "$@")
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -i)
+ include CP $2
+ ;;
+ -y)
+ JAVA_OPTS="-agentlib:yjpagent $JAVA_OPTS"
+ ;;
+ -g)
+ GC_OPTS="-verbosegc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:/tmp/gc.log"
+ ;;
+ -d)
+ JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n $JAVA_OPTS"
+ ;;
+ -h)
+ path_prepend DYLD_LIBRARY_PATH $2
+ JAVA_OPTS="-Xbootclasspath/a:$2 -agentlib:heapster $JAVA_OPTS"
+ ;;
+ --) shift; break;;
+ esac
+ shift
+done
+
+if [ $# -eq 0 ]; then
+ echo "usage: $0 [-i INCLUDE] [-h HEAPSTER] [-ygd] CLASS ..." >&2
+ echo " -i INCLUDE include in the classpath the project with the sbt root INCLUDE" >&2
+ echo " -h HEAPSTER use heapster in the directory HEAPSTER" >&2
+ echo " -y enable yourkit debugging" >&2
+ echo " -g enable GC debugging/logging" >&2
+ echo " -d enable JVM debugging" >&2
+ exit 1
+fi
+
+## Set up the classpath. Scala base jars first.
+path_prepend CP $root/project/boot/scala-2.8.1/lib/scala-library.jar
+path_prepend CP $root/project/boot/scala-2.8.1/lib/scala-compiler.jar
+
+# This goes last:
+include CP $root
+
+# Disable IPv6
+export JAVA_OPTS="-Djava.net.preferIPv4Stack=true $JAVA_OPTS"
+
+export JAVA_OPTS="$JAVA_OPTS -server -Xmx2G -XX:MaxPermSize=256m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC $GC_OPTS"
+exec java $JAVA_OPTS -cp $CP "$@"
45 src/main/scala/com/twitter/kestrelthrift/KestrelthriftServiceImpl.scala
@@ -0,0 +1,45 @@
+package com.twitter.kestrelthrift
+
+import java.util.concurrent.Executors
+import scala.collection.mutable
+import com.twitter.util._
+import config._
+
+class KestrelthriftServiceImpl(config: KestrelthriftServiceConfig) extends KestrelthriftServiceServer {
+ val serverName = "Kestrelthrift"
+ val thriftPort = config.thriftPort
+
+ /**
+ * These services are based on finagle, which implements a nonblocking server. If you
+ * are making blocking rpc calls, it's really important that you run these actions in
+ * a thread pool, so that you don't block the main event loop. This thread pool is only
+ * needed for these blocking actions. The code looks like:
+ *
+ * // Depends on com.twitter.util >= 1.6.10
+ * val futurePool = new FuturePool(Executors.newFixedThreadPool(config.threadPoolSize))
+ *
+ * def hello() = futurePool {
+ * someService.blockingRpcCall
+ * }
+ *
+ */
+
+ val database = new mutable.HashMap[String, String]()
+
+ def get(key: String) = {
+ database.get(key) match {
+ case None =>
+ log.debug("get %s: miss", key)
+ Future.exception(new KestrelthriftException("No such key"))
+ case Some(value) =>
+ log.debug("get %s: hit", key)
+ Future(value)
+ }
+ }
+
+ def put(key: String, value: String) = {
+ log.debug("put %s", key)
+ database(key) = value
+ Future.void
+ }
+}
12 src/main/scala/com/twitter/kestrelthrift/Main.scala
@@ -0,0 +1,12 @@
+package com.twitter.kestrelthrift
+
+import com.twitter.ostrich.admin.RuntimeEnvironment
+
+object Main {
+ def main(args: Array[String]) {
+ val env = RuntimeEnvironment(this, args)
+ val service = env.loadRuntimeConfig[KestrelthriftServiceServer]
+ service.start()
+ }
+}
+
14 src/main/scala/com/twitter/kestrelthrift/config/KestrelthriftServiceConfig.scala
@@ -0,0 +1,14 @@
+package com.twitter.kestrelthrift
+package config
+
+import com.twitter.logging.Logger
+import com.twitter.logging.config._
+import com.twitter.ostrich.admin.{RuntimeEnvironment, ServiceTracker}
+import com.twitter.ostrich.admin.config._
+import com.twitter.util.Config
+
+class KestrelthriftServiceConfig extends ServerConfig[KestrelthriftServiceServer] {
+ var thriftPort: Int = 9999
+
+ def apply(runtime: RuntimeEnvironment) = new KestrelthriftServiceImpl(this)
+}
20 src/main/thrift/kestrelthrift.thrift
@@ -0,0 +1,20 @@
+namespace java com.twitter.kestrelthrift.thrift
+namespace rb Kestrelthrift
+
+/**
+ * It's considered good form to declare an exception type for your service.
+ * Thrift will serialize and transmit them transparently.
+ */
+exception KestrelthriftException {
+ 1: string description
+}
+
+/**
+ * A simple memcache-like service, which stores strings by key/value.
+ * You should replace this with your actual service.
+ */
+service KestrelthriftService {
+ string get(1: string key) throws(1: KestrelthriftException ex)
+
+ void put(1: string key, 2: string value)
+}
29 src/scripts/console
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+$: << File.dirname(__FILE__) + "/../../../target/gen-rb"
+
+require "rubygems"
+require "bundler/setup"
+
+require "thrift"
+require "thrift_client"
+require "kestrelthrift_service"
+require "irb"
+
+class KestrelthriftClient < ThriftClient
+ DEFAULTS = { :transport_wrapper => Thrift::FramedTransport }
+ def initialize(servers = nil, options = {})
+ if servers.nil? or servers.empty?
+ STDERR.puts "No servers specified, using 127.0.0.1:9999"
+ servers = ['127.0.0.1:9999']
+ else
+ servers = Array(servers)
+ end
+
+ super(Kestrelthrift::KestrelthriftService::Client, servers, DEFAULTS.merge(options))
+ end
+end
+
+puts "Hint: the client is in the variable `$client`"
+$client = KestrelthriftClient.new ARGV.shift
+
+IRB.start
137 src/scripts/kestrelthrift.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+#
+# kestrelthrift init.d script.
+#
+# All java services require the same directory structure:
+# /usr/local/$APP_NAME
+# /var/log/$APP_NAME
+# /var/run/$APP_NAME
+
+APP_NAME="kestrelthrift"
+ADMIN_PORT="9900"
+VERSION="@VERSION@"
+APP_HOME="/usr/local/$APP_NAME/current"
+DAEMON="/usr/local/bin/daemon"
+
+JAR_NAME="$APP_NAME-$VERSION.jar"
+STAGE="production"
+
+HEAP_OPTS="-Xmx4096m -Xms4096m -XX:NewSize=768m"
+GC_OPTS="-XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy -XX:MaxGCPauseMillis=1000 -XX:GCTimeRatio=99"
+GC_LOG_OPTS="-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC"
+GC_LOG="-Xloggc:/var/log/$APP_NAME/gc.log"
+DEBUG_OPTS="-XX:ErrorFile=/var/log/$APP_NAME/java_error%p.log"
+JAVA_OPTS="-server -Dstage=$STAGE $GC_OPTS $GC_LOG_OPTS $GC_LOG $HEAP_OPTS $DEBUG_OPTS"
+
+pidfile="/var/run/$APP_NAME/$APP_NAME.pid"
+daemon_pidfile="/var/run/$APP_NAME/$APP_NAME-daemon.pid"
+daemon_args="--name $APP_NAME --pidfile $daemon_pidfile --core --chdir /"
+daemon_start_args="--stdout=/var/log/$APP_NAME/stdout --stderr=/var/log/$APP_NAME/error"
+
+function running() {
+ $DAEMON $daemon_args --running
+}
+
+function find_java() {
+ if [ ! -z "$JAVA_HOME" ]; then
+ return
+ fi
+ for dir in /opt/jdk /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home /usr/java/default; do
+ if [ -x $dir/bin/java ]; then
+ JAVA_HOME=$dir
+ break
+ fi
+ done
+}
+
+find_java
+
+
+case "$1" in
+ start)
+ echo -n "Starting $APP_NAME... "
+
+ if [ ! -r $APP_HOME/$JAR_NAME ]; then
+ echo "FAIL"
+ echo "*** $APP_NAME jar missing: $APP_HOME/$JAR_NAME - not starting"
+ exit 1
+ fi
+ if [ ! -x $JAVA_HOME/bin/java ]; then
+ echo "FAIL"
+ echo "*** $JAVA_HOME/bin/java doesn't exist -- check JAVA_HOME?"
+ exit 1
+ fi
+ if running; then
+ echo "already running."
+ exit 0
+ fi
+
+ ulimit -c unlimited || echo -n " (no coredump)"
+ $DAEMON $daemon_args $daemon_start_args -- sh -c "echo "'$$'" > $pidfile; exec ${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar ${APP_HOME}/${JAR_NAME}"
+ tries=0
+ while ! running; do
+ tries=$((tries + 1))
+ if [ $tries -ge 5 ]; then
+ echo "FAIL"
+ exit 1
+ fi
+ sleep 1
+ done
+ echo "done."
+ ;;
+
+ stop)
+ echo -n "Stopping $APP_NAME... "
+ if ! running; then
+ echo "wasn't running."
+ exit 0
+ fi
+
+ curl -m 5 -s http://localhost:${ADMIN_PORT}/shutdown.txt > /dev/null
+ tries=0
+ while running; do
+ tries=$((tries + 1))
+ if [ $tries -ge 15 ]; then
+ echo "FAILED SOFT SHUTDOWN, TRYING HARDER"
+ if [ -f $pidfile ]; then
+ kill $(cat $pidfile)
+ else
+ echo "CAN'T FIND PID, TRY KILL MANUALLY"
+ exit 1
+ fi
+ hardtries=0
+ while running; do
+ hardtries=$((hardtries + 1))
+ if [ $hardtries -ge 5 ]; then
+ echo "FAILED HARD SHUTDOWN, TRY KILL -9 MANUALLY"
+ exit 1
+ fi
+ sleep 1
+ done
+ fi
+ sleep 1
+ done
+ echo "done."
+ ;;
+
+ status)
+ if running; then
+ echo "$APP_NAME is running."
+ else
+ echo "$APP_NAME is NOT running."
+ fi
+ ;;
+
+ restart)
+ $0 stop
+ sleep 2
+ $0 start
+ ;;
+
+ *)
+ echo "Usage: /etc/init.d/${APP_NAME}.sh {start|stop|restart|status}"
+ exit 1
+ ;;
+esac
+
+exit 0
18 src/test/scala/com/twitter/kestrelthrift/AbstractSpec.scala
@@ -0,0 +1,18 @@
+package com.twitter.kestrelthrift
+
+import org.specs.Specification
+import com.twitter.ostrich.admin._
+import com.twitter.util._
+import com.twitter.conversions.time._
+
+abstract class AbstractSpec extends Specification {
+ val env = RuntimeEnvironment(this, Array("-f", "config/test.scala"))
+ lazy val kestrelthrift = {
+ val out = env.loadRuntimeConfig[KestrelthriftService]
+
+ // You don't really want the thrift server active, particularly if you
+ // are running repetitively via ~test
+ ServiceTracker.shutdown // all services
+ out
+ }
+}
14 src/test/scala/com/twitter/kestrelthrift/KestrelthriftServiceSpec.scala
@@ -0,0 +1,14 @@
+package com.twitter.kestrelthrift
+
+class KestrelthriftServiceSpec extends AbstractSpec {
+ "KestrelthriftService" should {
+
+ // TODO: Please implement your own tests.
+
+ "set a key, get a key" in {
+ kestrelthrift.put("name", "bluebird")()
+ kestrelthrift.get("name")() mustEqual "bluebird"
+ kestrelthrift.get("what?")() must throwA[Exception]
+ }
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.