Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

updated gem to work with sbt 0.11 and scala 2.9.1

  • Loading branch information...
commit 2f19693fc7c9fe0cf2ec56badbcebf28ff914317 1 parent 4550688
danielschonfeld authored
Showing with 902 additions and 7 deletions.
  1. +4 −0 README.rdoc
  2. +1 −1  VERSION
  3. +17 −6 bin/scala-bootstrapper
  4. +10 −0 lib/template2/.gitignore.erb
  5. +27 −0 lib/template2/Capfile
  6. +7 −0 lib/template2/Gemfile
  7. +24 −0 lib/template2/README.md
  8. +187 −0 lib/template2/TUTORIAL.md
  9. +22 −0 lib/template2/build.sbt.erb
  10. +41 −0 lib/template2/config/development.scala.erb
  11. +41 −0 lib/template2/config/production.scala.erb
  12. +41 −0 lib/template2/config/staging.scala.erb
  13. +26 −0 lib/template2/config/test.scala.erb
  14. +8 −0 lib/template2/console.erb
  15. +3 −0  lib/template2/project/plugins.sbt
  16. +125 −0 lib/template2/run
  17. +46 −0 lib/template2/src/main/scala/com/twitter/birdname/BirdNameConsoleClient.scala.erb
  18. +45 −0 lib/template2/src/main/scala/com/twitter/birdname/BirdNameServiceImpl.scala.erb
  19. +23 −0 lib/template2/src/main/scala/com/twitter/birdname/Main.scala.erb
  20. +14 −0 lib/template2/src/main/scala/com/twitter/birdname/config/BirdNameServiceConfig.scala.erb
  21. +20 −0 lib/template2/src/main/thrift/birdname.thrift.erb
  22. +137 −0 lib/template2/src/scripts/startup.sh
  23. +18 −0 lib/template2/src/test/scala/com/twitter/birdname/AbstractSpec.scala.erb
  24. +14 −0 lib/template2/src/test/scala/com/twitter/birdname/BirdNameServiceSpec.scala.erb
  25. +1 −0  scala-bootstrapper.gemspec
View
4 README.rdoc
@@ -1,3 +1,7 @@
+This gem produces a skeleton finagled service directory to be used with the new SBT 0.11 and Scala 2.9.1
+ :$ scala-bootstrapper -s birdname
+- - -
+
This creates a standard environment for your twitter-centric sbt/scala thrift service.
Building:
View
2  VERSION
@@ -1 +1 @@
-0.9.2
+0.9.3
View
23 bin/scala-bootstrapper
@@ -8,6 +8,7 @@ opts = Trollop::options do
opt :public, "Use the public twitter maven repo"
opt :namespace, "Use something besides com.twitter", :type => :string
opt :git, "Use Git to track updates to generated files"
+ opt :sbt11, "Setup an SBT 0.11 and Finagle2 skeleton"
end
@@ -55,6 +56,8 @@ project_name = ARGV.pop.camelize(true)
is_public = opts[:public]
namespace = opts[:namespace] || "com.twitter"
git = opts[:git]
+sbt11_requested = opts[:sbt11]
+
$overwrite_all = true if git
$ex_post_facto = false
$branch = 'master'
@@ -84,7 +87,11 @@ if git
end
end
-root = File.expand_path(File.dirname(__FILE__) + "/../lib/template")
+if sbt11_requested
+ root = File.expand_path(File.dirname(__FILE__) + "/../lib/template2")
+else
+ root = File.expand_path(File.dirname(__FILE__) + "/../lib/template")
+end
Dir["#{root}/**/*"].select{|path| File.file?(path)}.each do |path|
relative = path.sub("#{root}/", "")
@@ -95,12 +102,16 @@ Dir["#{root}/**/*"].select{|path| File.file?(path)}.each do |path|
print "File exists `#{relative}`, replace? ([Y]es, [N]o, [A]ll, [Q]uit)"
$stdout.flush
case STDIN.gets
- when /^y/i: # continue
- when /^n/i: next
- when /^a/i: $overwrite_all = true
- when /^q/i: exit(2)
+ when /^y/i
+ #continue
+ when /^n/i
+ next
+ when /^a/i
+ $overwrite_all = true
+ when /^q/i
+ exit(2)
else
- retry
+ redo
end
end
puts "writing #{target_path}"
View
10 lib/template2/.gitignore.erb
@@ -0,0 +1,10 @@
+*.class
+*.log
+
+# sbt specific
+dist/*
+target/
+lib_managed/
+src_managed/
+project/boot/
+project/plugins/project/
View
27 lib/template2/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, "birdname"
+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
+
View
7 lib/template2/Gemfile
@@ -0,0 +1,7 @@
+# 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"
View
24 lib/template2/README.md
@@ -0,0 +1,24 @@
+# Project BirdName
+
+Welcome to your birdname 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.
View
187 lib/template2/TUTORIAL.md
@@ -0,0 +1,187 @@
+# Welcome to BirdName!
+
+## 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/birdname/birdname-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 "birdname", there should be a `birdname.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/birdname.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 birdname
+ $ bundle install
+ $ ./dist/birdname/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:
+ BirdName/connects: 1
+ BirdName/requests: 2
+ BirdName/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 `BirdNameServiceImpl.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 BirdNameServiceImpl.scala, add:
+
+ import com.twitter.ostrich.stats.Stats
+
+Then inside get():
+
+ Stats.incr("birdname.gets")
+
+and inside put():
+
+ Stats.incr("birdname.puts")
+
+Then restart your server, talk to the server via console, and check
+your stats:
+
+ $ curl localhost:9900/stats.txt
+ counters:
+ BirdName/connects: 1
+ BirdName/requests: 2
+ BirdName/success: 2
+ birdname.gets: 1
+ birdname.puts: 1
+
+You can also time various things that your server is doing, for
+example:
+
+ Stats.time("birdname.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/birdname/BirdNameServiceSpec.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 birdname 1.0.0-SNAPSHOT against Scala 2.8.1
+ [info] using BirdNameProject 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.
View
22 lib/template2/build.sbt.erb
@@ -0,0 +1,22 @@
+import com.twitter.sbt._
+
+seq(CompileThriftScrooge.newSettings: _*)
+
+resolvers += "twitter-repo" at "http://maven.twttr.com"
+
+libraryDependencies ++= Seq(
+ "org.scala-lang" % "jline" % "2.9.1",
+ "thrift" % "libthrift" % "0.5.0" from "http://maven.twttr.com/thrift/libthrift/0.5.0/libthrift-0.5.0.jar",
+ "org.apache.thrift" % "libthrift" % "0.5.0" intransitive,
+ "com.twitter" %% "scrooge-runtime" % "1.1.3",
+ "com.twitter" %% "finagle-core" % "2.0.1",
+ "com.twitter" %% "finagle-thrift" % "2.0.1",
+ "com.twitter" %% "finagle-ostrich4" % "2.0.1"
+)
+
+mainClass in (Compile, run) := Some("com.twitter.birdname.Main")
+
+mainClass in (Compile, packageBin) := Some("com.twitter.birdname.Main")
+
+
+
View
41 lib/template2/config/development.scala.erb
@@ -0,0 +1,41 @@
+import com.twitter.conversions.time._
+import com.twitter.logging.config._
+import com.twitter.ostrich.admin.config._
+import com.twitter.birdname.config._
+
+// development mode.
+new BirdNameServiceConfig {
+
+ // 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 = "birdname.log"
+ roll = Policy.SigHup
+ }
+ } :: new LoggerConfig {
+ node = "stats"
+ level = Level.INFO
+ useParents = false
+ handlers = new FileHandlerConfig {
+ filename = "stats.log"
+ formatter = BareFormatterConfig
+ }
+ }
+}
View
41 lib/template2/config/production.scala.erb
@@ -0,0 +1,41 @@
+import com.twitter.conversions.time._
+import com.twitter.logging.config._
+import com.twitter.ostrich.admin.config._
+import com.twitter.birdname.config._
+
+// production mode.
+new BirdNameServiceConfig {
+
+ // 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/birdname/production.log"
+ roll = Policy.SigHup
+ }
+ } :: new LoggerConfig {
+ node = "stats"
+ level = Level.INFO
+ useParents = false
+ handlers = new FileHandlerConfig {
+ filename = "stats.log"
+ formatter = BareFormatterConfig
+ }
+ }
+}
View
41 lib/template2/config/staging.scala.erb
@@ -0,0 +1,41 @@
+import com.twitter.ostrich.admin.config._
+import com.twitter.conversions.time._
+import com.twitter.logging.config._
+import com.twitter.birdname.config._
+
+// staging mode.
+new BirdNameServiceConfig {
+
+ // 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/birdname/production.log"
+ roll = Policy.SigHup
+ }
+ } :: new LoggerConfig {
+ node = "stats"
+ level = Level.INFO
+ useParents = false
+ handlers = new FileHandlerConfig {
+ filename = "stats.log"
+ formatter = BareFormatterConfig
+ }
+ }
+}
View
26 lib/template2/config/test.scala.erb
@@ -0,0 +1,26 @@
+import com.twitter.conversions.time._
+import com.twitter.logging.config._
+import com.twitter.ostrich.admin.config._
+import com.twitter.birdname.config._
+
+// test mode.
+new BirdNameServiceConfig {
+
+ // 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
+ }
+}
View
8 lib/template2/console.erb
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [ $# -lt 2 ] ; then
+ echo "usage: console host port"
+ exit 0
+fi
+
+sbt "run-main com.twitter.birdname.BirdNameConsoleClient $1 $2"
View
3  lib/template2/project/plugins.sbt
@@ -0,0 +1,3 @@
+resolvers += "twitter-repo" at "http://maven.twttr.com"
+
+addSbtPlugin("com.twitter" %% "sbt-scrooge2" % "0.0.1")
View
125 lib/template2/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 "$@"
View
46 lib/template2/src/main/scala/com/twitter/birdname/BirdNameConsoleClient.scala.erb
@@ -0,0 +1,46 @@
+package com.twitter.birdname
+
+import com.twitter.finagle.builder.ClientBuilder
+import com.twitter.finagle.thrift.ThriftClientFramedCodec
+import java.net.InetSocketAddress
+
+import com.twitter.conversions.time._
+
+import scala.tools.nsc.interpreter._
+import scala.tools.nsc.interpreter.{JLineReader,JLineCompletion,ILoop,NamedParam}
+import scala.tools.nsc.Settings
+
+object BirdNameConsoleClient extends App {
+
+ val service = ClientBuilder()
+ .hosts(new InetSocketAddress(args(0),args(1).toInt))
+ .codec(ThriftClientFramedCodec())
+ .hostConnectionLimit(1)
+ .tcpConnectTimeout(3.seconds)
+ .build()
+
+ val client = new BirdNameService.FinagledClient(service)
+
+ val intLoop = new ILoop
+
+ Console.println("'client' has been binded. With it you can query different thrift methods your service exposes.")
+ intLoop.setPrompt("\nfinagle-client> ")
+
+ intLoop.settings = {
+ val s = new Settings(Console.println)
+ s.embeddedDefaults[BirdNameService.FinagledClient]
+ s.Yreplsync.value = true
+ s
+ }
+
+ intLoop.createInterpreter()
+ intLoop.in = new JLineReader(new JLineCompletion(intLoop))
+
+ intLoop.intp.beQuietDuring {
+ intLoop.intp.interpret("""def exit = println("Type :quit to resume program execution.")""")
+ intLoop.intp.bind(NamedParam("client",client))
+ }
+
+ intLoop.loop
+ intLoop.closeInterpreter
+}
View
45 lib/template2/src/main/scala/com/twitter/birdname/BirdNameServiceImpl.scala.erb
@@ -0,0 +1,45 @@
+package com.twitter.birdname
+
+import java.util.concurrent.Executors
+import scala.collection.mutable
+import com.twitter.util._
+import config._
+
+class BirdNameServiceImpl(config: BirdNameServiceConfig) extends BirdNameService.ThriftServer {
+ val serverName = "BirdName"
+ 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 BirdNameException("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.Unit
+ }
+}
View
23 lib/template2/src/main/scala/com/twitter/birdname/Main.scala.erb
@@ -0,0 +1,23 @@
+package com.twitter.birdname
+
+import com.twitter.logging.Logger
+import com.twitter.ostrich.admin.{RuntimeEnvironment, ServiceTracker}
+
+object Main {
+ val log = Logger.get(getClass)
+
+ def main(args: Array[String]) {
+ val runtime = RuntimeEnvironment(this, args)
+ val server = runtime.loadRuntimeConfig[BirdNameService.ThriftServer]
+ try {
+ log.info("Starting BirdNameService")
+ server.start()
+ } catch {
+ case e: Exception =>
+ log.error(e, "Failed starting BirdNameService, exiting")
+ ServiceTracker.shutdown()
+ System.exit(1)
+ }
+ }
+}
+
View
14 lib/template2/src/main/scala/com/twitter/birdname/config/BirdNameServiceConfig.scala.erb
@@ -0,0 +1,14 @@
+package com.twitter.birdname
+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 BirdNameServiceConfig extends ServerConfig[BirdNameService.ThriftServer] {
+ var thriftPort: Int = 9999
+
+ def apply(runtime: RuntimeEnvironment) = new BirdNameServiceImpl(this)
+}
View
20 lib/template2/src/main/thrift/birdname.thrift.erb
@@ -0,0 +1,20 @@
+namespace java com.twitter.birdname
+namespace rb BirdName
+
+/**
+ * It's considered good form to declare an exception type for your service.
+ * Thrift will serialize and transmit them transparently.
+ */
+exception BirdNameException {
+ 1: string description
+}
+
+/**
+ * A simple memcache-like service, which stores strings by key/value.
+ * You should replace this with your actual service.
+ */
+service BirdNameService {
+ string get(1: string key) throws(1: BirdNameException ex)
+
+ void put(1: string key, 2: string value)
+}
View
137 lib/template2/src/scripts/startup.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+#
+# birdname 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="birdname"
+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
View
18 lib/template2/src/test/scala/com/twitter/birdname/AbstractSpec.scala.erb
@@ -0,0 +1,18 @@
+package com.twitter.birdname
+
+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 birdName = {
+ val out = env.loadRuntimeConfig[BirdNameService]
+
+ // You don't really want the thrift server active, particularly if you
+ // are running repetitively via ~test
+ ServiceTracker.shutdown // all services
+ out
+ }
+}
View
14 lib/template2/src/test/scala/com/twitter/birdname/BirdNameServiceSpec.scala.erb
@@ -0,0 +1,14 @@
+package com.twitter.birdname
+
+class BirdNameServiceSpec extends AbstractSpec {
+ "BirdNameService" should {
+
+ // TODO: Please implement your own tests.
+
+ "set a key, get a key" in {
+ birdName.put("name", "bluebird")()
+ birdName.get("name")() mustEqual "bluebird"
+ birdName.get("what?")() must throwA[Exception]
+ }
+ }
+}
View
1  scala-bootstrapper.gemspec
@@ -45,6 +45,7 @@ Gem::Specification.new do |s|
"lib/template/src/scripts/startup.sh",
"lib/template/src/test/scala/com/twitter/birdname/AbstractSpec.scala.erb",
"lib/template/src/test/scala/com/twitter/birdname/BirdNameServiceSpec.scala.erb",
+ "lib/template2/**",
"scala-bootstrapper.gemspec",
"vendor/trollop.rb"
]
Please sign in to comment.
Something went wrong with that request. Please try again.