diff --git a/.gitignore b/.gitignore index 0a922a23..d32cd6df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .gradle/* -build/* \ No newline at end of file +build/* +example-app/build/* +example-app/.gradle/* +.DS_Store diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..77759ee0 --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +source "https://rubygems.org" + +gem 'redis' +gem 'rspec' +gem 'httparty' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..14a602d5 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,30 @@ +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.2.5) + httparty (0.12.0) + json (~> 1.8) + multi_xml (>= 0.5.2) + json (1.8.1) + multi_xml (0.5.5) + redis (3.1.0) + rspec (3.1.0) + rspec-core (~> 3.1.0) + rspec-expectations (~> 3.1.0) + rspec-mocks (~> 3.1.0) + rspec-core (3.1.4) + rspec-support (~> 3.1.0) + rspec-expectations (3.1.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.1.0) + rspec-mocks (3.1.1) + rspec-support (~> 3.1.0) + rspec-support (3.1.0) + +PLATFORMS + ruby + +DEPENDENCIES + httparty + redis + rspec diff --git a/example-app/build.gradle b/example-app/build.gradle new file mode 100644 index 00000000..16523dd2 --- /dev/null +++ b/example-app/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'java' +apply plugin: 'war' + +version = '0.1' + +repositories { + mavenCentral() +} + +dependencies { + providedCompile group: 'org.apache.tomcat', name: 'tomcat-catalina', version: '7.0.27' + compile group: 'redis.clients', name: 'jedis', version: '2.5.2' + compile group: 'com.sparkjava', name: 'spark-core', version: '1.1.1' + compile group: 'com.google.code.gson', name: 'gson', version: '2.3' + compile group: 'org.slf4j', name: 'slf4j-simple',version: '1.7.5' + providedCompile project(":tomcat-redis-session-manager") +} + +war { + //webAppDirName = 'source/main/' + + //from 'src/rootContent' // adds a file-set to the root of the archive + //webInf { from 'src/additionalWebInf' } // adds a file-set to the WEB-INF dir. + //classpath fileTree('additionalLibs') // adds a file-set to the WEB-INF/lib dir. + //classpath configurations.moreLibs // adds a configuration to the WEB-INF/lib dir. +} \ No newline at end of file diff --git a/example-app/settings.gradle b/example-app/settings.gradle new file mode 100644 index 00000000..f48382b1 --- /dev/null +++ b/example-app/settings.gradle @@ -0,0 +1,2 @@ +include ":tomcat-redis-session-manager" +project(":tomcat-redis-session-manager").projectDir = new File(rootDir, "../") diff --git a/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/JsonTransformerRoute.java b/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/JsonTransformerRoute.java new file mode 100644 index 00000000..4238e523 --- /dev/null +++ b/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/JsonTransformerRoute.java @@ -0,0 +1,28 @@ +package com.orangefunction.tomcatredissessionmanager.exampleapp; + +import com.google.gson.Gson; +import com.radiadesign.catalina.session.RedisSession; +import java.util.HashMap; +import java.util.Collections; +import spark.ResponseTransformerRoute; +import spark.Session; +import javax.servlet.http.HttpSession; + +public abstract class JsonTransformerRoute extends ResponseTransformerRoute { + + private Gson gson = new Gson(); + + protected JsonTransformerRoute(String path) { + super(path); + } + + protected JsonTransformerRoute(String path, String acceptType) { + super(path, acceptType); + } + + @Override + public String render(Object jsonObject) { + return gson.toJson(jsonObject); + } + +} \ No newline at end of file diff --git a/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/SessionJsonTransformerRoute.java b/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/SessionJsonTransformerRoute.java new file mode 100644 index 00000000..cb6faf8e --- /dev/null +++ b/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/SessionJsonTransformerRoute.java @@ -0,0 +1,52 @@ +package com.orangefunction.tomcatredissessionmanager.exampleapp; + +import com.google.gson.Gson; +import com.radiadesign.catalina.session.RedisSession; +import java.util.HashMap; +import java.util.Map; +import java.util.Collections; +import spark.ResponseTransformerRoute; +import spark.Session; +import javax.servlet.http.HttpSession; + +public abstract class SessionJsonTransformerRoute extends ResponseTransformerRoute { + + private Gson gson = new Gson(); + + protected SessionJsonTransformerRoute(String path) { + super(path); + } + + protected SessionJsonTransformerRoute(String path, String acceptType) { + super(path, acceptType); + } + + @Override + public String render(Object object) { + if (object instanceof Object[]) { + Object[] tuple = (Object[])object; + Session sparkSession = (Session)tuple[0]; + HttpSession session = (HttpSession)(sparkSession).raw(); + HashMap map = new HashMap(); + map.putAll((Map)tuple[1]); + map.put("sessionId", session.getId()); + return gson.toJson(map); + } else if (object instanceof Session) { + Session sparkSession = (Session)object; + HashMap sessionMap = new HashMap(); + if (null != sparkSession) { + HttpSession session = (HttpSession)(sparkSession).raw(); + sessionMap.put("sessionId", session.getId()); + HashMap attributesMap = new HashMap(); + for (String key : Collections.list(session.getAttributeNames())) { + attributesMap.put(key, session.getAttribute(key)); + } + sessionMap.put("attributes", attributesMap); + } + return gson.toJson(sessionMap); + } else { + return "{}"; + } + } + +} \ No newline at end of file diff --git a/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/WebApp.java b/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/WebApp.java new file mode 100644 index 00000000..cdc8949d --- /dev/null +++ b/example-app/src/main/java/com/orangefunction/tomcatredissessionmanager/exampleapp/WebApp.java @@ -0,0 +1,268 @@ +package com.orangefunction.tomcatredissessionmanager.exampleapp; + +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import static spark.Spark.*; +import spark.*; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Protocol; +import com.radiadesign.catalina.session.*; +import org.apache.catalina.session.StandardSession; +import org.apache.catalina.session.StandardSessionFacade; +import org.apache.catalina.core.ApplicationContextFacade; +import org.apache.catalina.core.ApplicationContext; +import org.apache.catalina.core.StandardContext; +import java.lang.reflect.Field; +import javax.servlet.*; + +public class WebApp implements spark.servlet.SparkApplication { + + protected String redisHost = "localhost"; + protected int redisPort = 6379; + protected int redisDatabase = 0; + protected String redisPassword = null; + protected int redisTimeout = Protocol.DEFAULT_TIMEOUT; + protected JedisPool redisConnectionPool; + + private void initializeJedisConnectionPool() { + try { + // TODO: Allow configuration of pool (such as size...) + redisConnectionPool = new JedisPool(new JedisPoolConfig(), redisHost, redisPort, redisTimeout, redisPassword); + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected Jedis acquireConnection() { + if (null == redisConnectionPool) { + initializeJedisConnectionPool(); + } + Jedis jedis = redisConnectionPool.getResource(); + + if (redisDatabase != 0) { + jedis.select(redisDatabase); + } + + return jedis; + } + + protected void returnConnection(Jedis jedis, Boolean error) { + if (error) { + redisConnectionPool.returnBrokenResource(jedis); + } else { + redisConnectionPool.returnResource(jedis); + } + } + + protected void returnConnection(Jedis jedis) { + returnConnection(jedis, false); + } + + protected RedisSessionManager getRedisSessionManager(Request request) { + RedisSessionManager sessionManager = null; + ApplicationContextFacade appContextFacadeObj = (ApplicationContextFacade)request.session().raw().getServletContext(); + try { + Field applicationContextField = appContextFacadeObj.getClass().getDeclaredField("context"); + applicationContextField.setAccessible(true); + ApplicationContext appContextObj = (ApplicationContext)applicationContextField.get(appContextFacadeObj); + Field standardContextField = appContextObj.getClass().getDeclaredField("context"); + standardContextField.setAccessible(true); + StandardContext standardContextObj = (StandardContext)standardContextField.get(appContextObj); + sessionManager = (RedisSessionManager)standardContextObj.getManager(); + } catch (Exception e) { } + return sessionManager; + } + + public void init() { + + // /session + + get(new SessionJsonTransformerRoute("/session", "application/json") { + @Override + public Object handle(Request request, Response response) { + return request.session(false); + } + }); + + put(new SessionJsonTransformerRoute("/session", "application/json") { + @Override + public Object handle(Request request, Response response) { + Session session = request.session(); + for (String key : request.queryParams()) { + session.attribute(key, request.queryParams(key)); + } + return session; + } + }); + + post(new SessionJsonTransformerRoute("/session", "application/json") { + @Override + public Object handle(Request request, Response response) { + Session session = request.session(); + for (String key : request.queryParams()) { + session.attribute(key, request.queryParams(key)); + } + return session; + } + }); + + delete(new SessionJsonTransformerRoute("/session", "application/json") { + @Override + public Object handle(Request request, Response response) { + request.session().raw().invalidate(); + return null; + } + }); + + + // /session/attributes + + get(new SessionJsonTransformerRoute("/session/attributes", "application/json") { + @Override + public Object handle(Request request, Response response) { + HashMap map = new HashMap(); + map.put("keys", request.session().attributes()); + return new Object[]{request.session(), map}; + } + }); + + get(new SessionJsonTransformerRoute("/session/attributes/:key", "application/json") { + @Override + public Object handle(Request request, Response response) { + String key = request.params(":key"); + HashMap map = new HashMap(); + map.put("key", key); + map.put("value", request.session().attribute(key)); + return new Object[]{request.session(), map}; + } + }); + + post(new SessionJsonTransformerRoute("/session/attributes/:key", "application/json") { + @Override + public Object handle(Request request, Response response) { + String key = request.params(":key"); + String oldValue = request.session().attribute(key); + request.session().attribute(key, request.queryParams("value")); + HashMap map = new HashMap(); + map.put("key", key); + map.put("value", request.session().attribute(key)); + map.put("oldValue", oldValue); + if (null != request.queryParams("sleep")) { + try { + java.lang.Thread.sleep(Integer.parseInt(request.queryParams("sleep"))); + } catch (InterruptedException e) {} + } + return new Object[]{request.session(), map}; + } + }); + + delete(new SessionJsonTransformerRoute("/session/attributes/:key", "application/json") { + @Override + public Object handle(Request request, Response response) { + String key = request.params(":key"); + String oldValue = request.session().attribute(key); + request.session().raw().removeAttribute(key); + HashMap map = new HashMap(); + map.put("key", key); + map.put("value", request.session().attribute(key)); + map.put("oldValue", oldValue); + return new Object[]{request.session(), map}; + } + }); + + + // /sessions + + get(new JsonTransformerRoute("/sessions", "application/json") { + @Override + public Object handle(Request request, Response response) { + Jedis jedis = null; + Boolean error = true; + try { + jedis = acquireConnection(); + Set keySet = jedis.keys("*"); + error = false; + return keySet.toArray(new String[keySet.size()]); + } finally { + if (jedis != null) { + returnConnection(jedis, error); + } + } + } + }); + + delete(new JsonTransformerRoute("/sessions", "application/json") { + @Override + public Object handle(Request request, Response response) { + Jedis jedis = null; + Boolean error = true; + try { + jedis = acquireConnection(); + jedis.flushDB(); + Set keySet = jedis.keys("*"); + error = false; + return keySet.toArray(new String[keySet.size()]); + } finally { + if (jedis != null) { + returnConnection(jedis, error); + } + } + } + }); + + + // /settings + + get(new SessionJsonTransformerRoute("/settings/:key", "application/json") { + @Override + public Object handle(Request request, Response response) { + String key = request.params(":key"); + HashMap map = new HashMap(); + map.put("key", key); + + RedisSessionManager manager = getRedisSessionManager(request); + if (null != manager) { + if (key.equals("saveOnChange")) { + map.put("value", new Boolean(manager.getSaveOnChange())); + } else if (key.equals("maxInactiveInterval")) { + map.put("value", new Integer(manager.getMaxInactiveInterval())); + } + } else { + map.put("error", new Boolean(true)); + } + + return new Object[]{request.session(), map}; + } + }); + + post(new SessionJsonTransformerRoute("/settings/:key", "application/json") { + @Override + public Object handle(Request request, Response response) { + String key = request.params(":key"); + String value = request.queryParams("value"); + HashMap map = new HashMap(); + map.put("key", key); + + RedisSessionManager manager = getRedisSessionManager(request); + if (null != manager) { + if (key.equals("saveOnChange")) { + manager.setSaveOnChange(Boolean.parseBoolean(value)); + map.put("value", new Boolean(manager.getSaveOnChange())); + } else if (key.equals("maxInactiveInterval")) { + manager.setMaxInactiveInterval(Integer.parseInt(value)); + map.put("value", new Integer(manager.getMaxInactiveInterval())); + } + } else { + map.put("error", new Boolean(true)); + } + + return new Object[]{request.session(), map}; + } + }); + + } + +} diff --git a/example-app/src/main/webapp/META-INF/context.xml b/example-app/src/main/webapp/META-INF/context.xml new file mode 100644 index 00000000..89fb706c --- /dev/null +++ b/example-app/src/main/webapp/META-INF/context.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/example-app/src/main/webapp/WEB-INF/web.xml b/example-app/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..dbe44d69 --- /dev/null +++ b/example-app/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,22 @@ + + + + Tomcat Redis Session Manager Example App + + SparkFilter + spark.servlet.SparkFilter + + applicationClass + com.orangefunction.tomcatredissessionmanager.exampleapp.WebApp + + + + + SparkFilter + /* + + + + diff --git a/spec/requests/sessions_spec.rb b/spec/requests/sessions_spec.rb new file mode 100644 index 00000000..2334986b --- /dev/null +++ b/spec/requests/sessions_spec.rb @@ -0,0 +1,195 @@ +require 'spec_helper' +#require "rails_helper" +#require 'action_controller' +#require 'action_view' +#require 'active_support' +#require 'active_support/core_ext' + +#require 'active_support/all' +#require 'action_controller' +#require 'action_dispatch' +# +#module Rails +# class App +# def env_config; {} end +# def routes +# return @routes if defined?(@routes) +# @routes = ActionDispatch::Routing::RouteSet.new +# @routes.draw do +# resources :posts # Replace with your own needs +# end +# @routes +# end +# end +# +# def self.application +# @app ||= App.new +# end +#end +# +#require 'rspec/rails' + +SESSION_PATH = '/session' +SESSIONS_PATH = '/sessions' +SESSION_ATTRIBUTES_PATH = '/session/attributes' +SETTINGS_PATH = '/settings' + +describe "Tomcat Redis Sessions", type: :controller do + + describe 'session creation' do + + it 'should begin without a session' do + get(SESSION_PATH) + json.should_not have_key('sessionId') + end + + it 'should generate a session ID' do + post(SESSION_PATH) + json.should have_key('sessionId') + end + + it 'should not create a session when requesting session creation with existing session ID' do + pending + end + + it 'should detect a session ID collision and generate a new session ID' do + pending + end + + it 'should detect and report race conditions when creating new sessions' do + pending + end + end + + describe 'session updating' do + it 'should support setting a value in the session' do + post(SESSION_PATH, body: {param1: '5'}) + json['attributes'].should have_key('param1') + json['attributes']['param1'].should == '5' + end + + it 'should support updating a value in the session' do + post(SESSION_PATH, body: {param1: '5'}) + json['attributes'].should have_key('param1') + json['attributes']['param1'].should == '5' + + put(SESSION_PATH, query: {param1: '6'}) + json['attributes']['param1'].should == '6' + end + + it 'should persist session attributes between requests' do + post(SESSION_PATH, body: {param1: '5'}) + get(SESSION_PATH) + json['attributes']['param1'].should == '5' + end + + it 'should persist updated session attributes between requests' do + post(SESSION_PATH, body: {param1: '5'}) + put(SESSION_PATH, query: {param1: '6'}) + get(SESSION_PATH) + json['attributes']['param1'].should == '6' + end + + it 'should support removing a value in the session' do + post("#{SESSION_ATTRIBUTES_PATH}/param1", body: {value: '5'}) + get(SESSION_ATTRIBUTES_PATH) + json['keys'].should include('param1') + + delete("#{SESSION_ATTRIBUTES_PATH}/param1") + get(SESSION_ATTRIBUTES_PATH) + json['keys'].should_not include('param1') + end + + it 'should default to last-write-wins behavior for simultaneous updates' do + post(SESSION_PATH, body: {param1: '5'}) + + # This is not a perfect guarantee, but in general we're assuming + # that the requests will happen in the following order: + # - Post(value=7) starts + # - Post(value=6) starts + # - Post(value=6) finishes + # - Get() returns 6 + # - Post(value=7) finishes + # - Get() returns 7 + winner = Thread.new do + post("#{SESSION_ATTRIBUTES_PATH}/param1", body: {value: '7', sleep: 2000}) + end + sleep 1 + post("#{SESSION_ATTRIBUTES_PATH}/param1", body: {value: '6'}) + # Verify our assumption that this request loaded it's session + # before the request Post(value=7) finished. + json['oldValue'].should == '5' + get("#{SESSION_ATTRIBUTES_PATH}/param1") + json['value'].should == '6' + + winner.join + + get("#{SESSION_ATTRIBUTES_PATH}/param1") + json['value'].should == '7' + end + + describe "change on save" do + + before :each do + get("#{SETTINGS_PATH}/saveOnChange") + @oldSaveOnChangeValue = json['value'] + post("#{SETTINGS_PATH}/saveOnChange", body: {value: 'true'}) + end + + after :each do + post("#{SETTINGS_PATH}/saveOnChange", body: {value: @oldSaveOnChangeValue}) + end + + it 'should support persisting the session on change to minimize race conditions on simultaneous updates' do + post(SESSION_PATH, body: {param2: '5'}) + get("#{SESSION_ATTRIBUTES_PATH}/param2") + json['value'].should == '5' + + # This is not a perfect guarantee, but in general we're assuming + # that the requests will happen in the following order: + # - Post(value=5) starts + # - Post(value=6) starts + # - Post(value=6) finishes + # - Get() returns 6 + # - Post(value=5) finishes + # - Get() returns 6 (because the change value=5 saved immediately rather than on request finish) + long_request = Thread.new do + post("#{SESSION_ATTRIBUTES_PATH}/param2", body: {value: '5', sleep: 2000}) + end + + post("#{SESSION_ATTRIBUTES_PATH}/param2", body: {value: '6'}) + get("#{SESSION_ATTRIBUTES_PATH}/param2") + json['value'].should == '6' + + long_request.join + + get("#{SESSION_ATTRIBUTES_PATH}/param2") + json['value'].should == '6' + end + end + end + + describe 'session expiration' do + + before :each do + get("#{SETTINGS_PATH}/maxInactiveInterval") + @oldMaxInactiveIntervalValue = json['value'] + post("#{SETTINGS_PATH}/maxInactiveInterval", body: {value: '1'}) + end + + after :each do + post("#{SETTINGS_PATH}/maxInactiveInterval", body: {value: @oldMaxInactiveIntervalValue}) + end + + it 'should no longer contain a session after the expiration timeout has passed' do + post(SESSION_PATH) + created_session_id = json['sessionId'] + get(SESSION_PATH) + json['sessionId'].should == created_session_id + sleep 1.0 + get(SESSION_PATH) + json['sessionId'].should be_nil + end + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..2a190945 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,61 @@ +require File.expand_path('../support/requests_helper', __FILE__) + +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# Require this file using `require "spec_helper"` to ensure that it is only +# loaded once. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + config.treat_symbols_as_metadata_keys_with_true_values = true + config.run_all_when_everything_filtered = true + config.filter_run :focus + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = 'random' + + config.include RequestsHelper + + config.before :all do + root_project_path = File.expand_path('../../', __FILE__) + vagrant_path = File.join(root_project_path, 'vagrant', 'tomcat-redis-example') + example_app_path = File.join(root_project_path, 'example-app') + #unless `cd #{vagrant_path} && vagrant status --machine-readable | awk -F, '$3 ~ /^state$/ { print $4}'`.strip == 'running' + # raise "Expected vagrant to be running." + #end + + # build manager + build_manager_command = <<-eos + cd #{root_project_path} \ + && gradle clean \ + && gradle build + eos + `#{build_manager_command}` + + # build example app + build_example_app_command = <<-eos + cd #{example_app_path} \ + && gradle clean \ + && gradle war + eos + `#{build_example_app_command}` + + deploy_command = <<-eos + cd #{vagrant_path} \ + && vagrant ssh -c "\ + sudo service tomcat7 stop \ + && sudo mkdir -p /var/lib/tomcat7/lib \ + && sudo rm -rf /var/lib/tomcat7/lib/tomcat-redis-session-manager* \ + && sudo cp /opt/tomcat-redis-session-manager/build/libs/tomcat-redis-session-manager*.jar /var/lib/tomcat7/lib/ \ + && sudo rm -rf /var/lib/tomcat7/webapps/example* \ + && sudo cp /opt/tomcat-redis-session-manager/example-app/build/libs/example-app*.war /var/lib/tomcat7/webapps/example.war \ + && sudo rm -f /var/log/tomcat7/* \ + && sudo service tomcat7 start \ + " + eos + `#{deploy_command}` + end +end diff --git a/spec/support/requests_helper.rb b/spec/support/requests_helper.rb new file mode 100644 index 00000000..6ffdeb26 --- /dev/null +++ b/spec/support/requests_helper.rb @@ -0,0 +1,67 @@ +require 'httparty' + +module RequestsHelper + + class ExampleAppClient + include ::HTTParty + base_uri 'http://172.28.128.3:8080/example' + end + + def client + @client ||= ExampleAppClient.new + end + + def get(path, options={}) + send_request(:get, path, options) + end + + def put(path, options={}) + send_request(:put, path, options) + end + + def post(path, options={}) + send_request(:post, path, options) + end + + def delete(path, options={}) + send_request(:delete, path, options) + end + + def send_request(method, path, options={}) + options ||= {} + headers = options[:headers] || {} + if cookie && !options.key('Cookie') + headers['Cookie'] = cookie + end + options = options.merge(headers: headers) + self.response = self.client.class.send(method, path, options) + end + + def request + response.request + end + + def response + @response + end + + def response=(r) + if r + if r.headers.key?('Set-Cookie') + @cookie = r.headers['Set-Cookie'] + end + else + @cookie = nil + end + @json = nil + @response = r + end + + def cookie + @cookie + end + + def json + @json ||= JSON.parse(response.body) + end +end diff --git a/vagrant/tomcat-redis-example/.gitignore b/vagrant/tomcat-redis-example/.gitignore new file mode 100644 index 00000000..cbccf1ea --- /dev/null +++ b/vagrant/tomcat-redis-example/.gitignore @@ -0,0 +1,20 @@ +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +pkg/ + +# Berkshelf +.vagrant +/cookbooks +Berksfile.lock + +# Bundler +Gemfile.lock +bin/* +.bundle/* + +.kitchen/ +.kitchen.local.yml diff --git a/vagrant/tomcat-redis-example/.kitchen.yml b/vagrant/tomcat-redis-example/.kitchen.yml new file mode 100644 index 00000000..8e312076 --- /dev/null +++ b/vagrant/tomcat-redis-example/.kitchen.yml @@ -0,0 +1,15 @@ +--- +driver: + name: vagrant + +provisioner: + name: chef_solo + +platforms: + - name: ubuntu-12.04 + - name: centos-6.4 + +suites: + - name: default + run_list: + attributes: diff --git a/vagrant/tomcat-redis-example/Berksfile b/vagrant/tomcat-redis-example/Berksfile new file mode 100644 index 00000000..e1d49d3f --- /dev/null +++ b/vagrant/tomcat-redis-example/Berksfile @@ -0,0 +1,5 @@ +source "https://api.berkshelf.com" + +metadata + +cookbook 'java', github: 'agileorbit-cookbooks/java', branch: 'master' diff --git a/vagrant/tomcat-redis-example/Gemfile b/vagrant/tomcat-redis-example/Gemfile new file mode 100644 index 00000000..55f8096e --- /dev/null +++ b/vagrant/tomcat-redis-example/Gemfile @@ -0,0 +1,18 @@ +source 'https://rubygems.org' + +gem 'berkshelf' + +# Uncomment these lines if you want to live on the Edge: +# +# group :development do +# gem "berkshelf", github: "berkshelf/berkshelf" +# gem "vagrant", github: "mitchellh/vagrant", tag: "v1.5.2" +# end +# +# group :plugins do +# gem "vagrant-berkshelf", github: "berkshelf/vagrant-berkshelf" +# gem "vagrant-omnibus", github: "schisamo/vagrant-omnibus" +# end + +gem 'test-kitchen' +gem 'kitchen-vagrant' diff --git a/vagrant/tomcat-redis-example/Thorfile b/vagrant/tomcat-redis-example/Thorfile new file mode 100644 index 00000000..b23ee163 --- /dev/null +++ b/vagrant/tomcat-redis-example/Thorfile @@ -0,0 +1,12 @@ +# encoding: utf-8 + +require 'bundler' +require 'bundler/setup' +require 'berkshelf/thor' + +begin + require 'kitchen/thor_tasks' + Kitchen::ThorTasks.new +rescue LoadError + puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV['CI'] +end diff --git a/vagrant/tomcat-redis-example/Vagrantfile b/vagrant/tomcat-redis-example/Vagrantfile new file mode 100644 index 00000000..0a592918 --- /dev/null +++ b/vagrant/tomcat-redis-example/Vagrantfile @@ -0,0 +1,106 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.require_version ">= 1.5.0" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + # All Vagrant configuration is done here. The most common configuration + # options are documented and commented below. For a complete reference, + # please see the online documentation at vagrantup.com. + + config.vm.hostname = "tomcat-redis-example-berkshelf" + + # Set the version of chef to install using the vagrant-omnibus plugin + config.omnibus.chef_version = '11.10' + + # Every Vagrant virtual environment requires a box to build off of. + config.vm.box = "ubuntu/trusty64" + + # The url from where the 'config.vm.box' box will be fetched if it + # doesn't already exist on the user's system. + config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box" + + # Assign this VM to a host-only network IP, allowing you to access it + # via the IP. Host-only networks can talk to the host machine as well as + # any other machines on the same network, but cannot be accessed (through this + # network interface) by any external networks. + config.vm.network :private_network, type: "dhcp" + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + config.vm.synced_folder "../../", "/opt/tomcat-redis-session-manager" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider :virtualbox do |vb| + # # Don't boot with headless mode + # vb.gui = true + # + # # Use VBoxManage to customize the VM. For example to change memory: + # vb.customize ["modifyvm", :id, "--memory", "1024"] + # end + # + # View the documentation for the provider you're using for more + # information on available options. + + # The path to the Berksfile to use with Vagrant Berkshelf + # config.berkshelf.berksfile_path = "./Berksfile" + + # Enabling the Berkshelf plugin. To enable this globally, add this configuration + # option to your ~/.vagrant.d/Vagrantfile file + config.berkshelf.enabled = true + + # An array of symbols representing groups of cookbook described in the Vagrantfile + # to exclusively install and copy to Vagrant's shelf. + # config.berkshelf.only = [] + + # An array of symbols representing groups of cookbook described in the Vagrantfile + # to skip installing and copying to Vagrant's shelf. + # config.berkshelf.except = [] + + config.vm.provision :chef_solo do |chef| + chef.json = { + mysql: { + server_root_password: 'rootpass', + server_debian_password: 'debpass', + server_repl_password: 'replpass' + } + } + + chef.run_list = [ + "recipe[tomcat-redis-example::default]" + ] + end + + if Vagrant.has_plugin?("vagrant-cachier") + # Configure cached packages to be shared between instances of the same base box. + # More info on http://fgrehm.viewdocs.io/vagrant-cachier/usage + config.cache.scope = :box + + # If you are using VirtualBox, you might want to use that to enable NFS for + # shared folders. This is also very useful for vagrant-libvirt if you want + # bi-directional sync + config.cache.synced_folder_opts = { + type: :nfs, + # The nolock option can be useful for an NFSv3 client that wants to avoid the + # NLM sideband protocol. Without this option, apt-get might hang if it tries + # to lock files needed for /var/cache/* operations. All of this can be avoided + # by using NFSv4 everywhere. Please note that the tcp option is not the default. + mount_options: ['rw', 'vers=3', 'tcp', 'nolock'] + } + # For more information please check http://docs.vagrantup.com/v2/synced-folders/basic_usage.html + end +end diff --git a/vagrant/tomcat-redis-example/chefignore b/vagrant/tomcat-redis-example/chefignore new file mode 100644 index 00000000..138a808b --- /dev/null +++ b/vagrant/tomcat-redis-example/chefignore @@ -0,0 +1,94 @@ +# Put files/directories that should be ignored in this file when uploading +# or sharing to the community site. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +Guardfile +Procfile + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +cookbooks/* +tmp + +# Cookbooks # +############# +CONTRIBUTING +CHANGELOG* + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile + +# Travis # +########## +.travis.yml diff --git a/vagrant/tomcat-redis-example/metadata.rb b/vagrant/tomcat-redis-example/metadata.rb new file mode 100644 index 00000000..04036947 --- /dev/null +++ b/vagrant/tomcat-redis-example/metadata.rb @@ -0,0 +1,15 @@ +name 'tomcat-redis-example' +maintainer 'James Coleman' +maintainer_email 'jtc331@gmail.com' +license 'MIT' +description 'Installs/Configures tomcat-redis-example' +long_description 'Installs/Configures tomcat-redis-example' +version '0.1.0' + +depends 'apt', '~> 2.4' +depends 'tomcat', '~> 0.16.2' +depends 'redisio', '~> 2.2.3' + +%w{ ubuntu debian }.each do |os| + supports os +end \ No newline at end of file diff --git a/vagrant/tomcat-redis-example/recipes/default.rb b/vagrant/tomcat-redis-example/recipes/default.rb new file mode 100644 index 00000000..f2f4396a --- /dev/null +++ b/vagrant/tomcat-redis-example/recipes/default.rb @@ -0,0 +1,44 @@ +node.set['java']['jdk_version'] = '7' + +node.set["tomcat"]["base_version"] = 7 +node.set['tomcat']['user'] = "tomcat#{node["tomcat"]["base_version"]}" +node.set['tomcat']['group'] = "tomcat#{node["tomcat"]["base_version"]}" +node.set['tomcat']['home'] = "/usr/share/tomcat#{node["tomcat"]["base_version"]}" +node.set['tomcat']['base'] = "/var/lib/tomcat#{node["tomcat"]["base_version"]}" +node.set['tomcat']['config_dir'] = "/etc/tomcat#{node["tomcat"]["base_version"]}" +node.set['tomcat']['log_dir'] = "/var/log/tomcat#{node["tomcat"]["base_version"]}" +node.set['tomcat']['tmp_dir'] = "/tmp/tomcat#{node["tomcat"]["base_version"]}-tmp" +node.set['tomcat']['work_dir'] = "/var/cache/tomcat#{node["tomcat"]["base_version"]}" +node.set['tomcat']['context_dir'] = "#{node["tomcat"]["config_dir"]}/Catalina/localhost" +node.set['tomcat']['webapp_dir'] = "/var/lib/tomcat#{node["tomcat"]["base_version"]}/webapps" +node.set['tomcat']['lib_dir'] = "#{node["tomcat"]["home"]}/lib" +node.set['tomcat']['endorsed_dir'] = "#{node["tomcat"]["lib_dir"]}/endorsed" + +#Chef::Log.info "node: #{node.to_hash.inspect}" + +include_recipe 'redisio' +include_recipe 'redisio::enable' + +include_recipe 'tomcat' + +lib_dir = File.join(node['tomcat']['base'], 'lib') + +directory(lib_dir) do + user node['tomcat']['user'] + group node['tomcat']['group'] +end + +remote_file(File.join(lib_dir, 'spark-core-1.1.1.jar')) do + source 'http://central.maven.org/maven2/com/sparkjava/spark-core/1.1.1/spark-core-1.1.1.jar' + action :create_if_missing +end + +remote_file(File.join(lib_dir, 'commons-pool2-2.2.jar')) do + source 'http://central.maven.org/maven2/org/apache/commons/commons-pool2/2.2/commons-pool2-2.2.jar' + action :create_if_missing +end + +remote_file(File.join(lib_dir, 'jedis-2.5.2.jar')) do + source 'http://central.maven.org/maven2/redis/clients/jedis/2.5.2/jedis-2.5.2.jar' + action :create_if_missing +end