multibinder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 04308acfb4e6a38f2ec0d7bd53b3bf921328ece0
4
+ data.tar.gz: cd6936ec293af4d11edf3d13e6e26a402c6dcb9e
5
+ SHA512:
6
+ metadata.gz: 071505702ac618577faf5de290e3b87c6b0f52d4579fa08d259b501509d1b47a2b5e040eedabc32438e3e2b5bbe77cbbcc577269dba62b8d7fc69fb2eeac1e46
7
+ data.tar.gz: 7fbf21103b70a01ad7e87768774f6fd37eef0980f4375db09e3bb6996685969068b6b57ce1711b6962040e54177437077bec8226a8bba2d9cb5dcccfd0ec949b
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in binder.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Theo Julienne
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ ### multibinder
2
+
3
+ multibinder is a tiny ruby server that makes writing zero-downtime-reload services simpler. It accepts connections on a UNIX domain socket and binds an arbitrary number of LISTEN sockets given their ip+port combinations. When a bind is requested, the LISTEN socket is sent over the UNIX domain socket using ancillary data. Subsequent identical binds receive the same LISTEN socket.
4
+
5
+ multibinder runs on its own, separate from the daemons that use the sockets. multibinder can be re-exec itself to take upgrades by sending it a `SIGUSR1` - existing binds will be retained across re-execs.
6
+
7
+ #### Server usage
8
+
9
+ After installing multibinder, you can run the multibinder daemon:
10
+
11
+ ```
12
+ bundle exec multibinder /path/to/control.sock
13
+ ```
14
+
15
+ #### Client usage
16
+
17
+ The multibinder library retrieves a socket from a local multibinder server, communicating over the socket you specify in the `MULTIBINDER_SOCK` environment variable (which has to be the same as specified when running multibinder, and the user must have permission to access the file).
18
+
19
+ ```ruby
20
+ require 'multibinder'
21
+
22
+ server = MultiBinder.bind '127.0.0.1', 8000
23
+
24
+ # use the server socket
25
+ # ... server.accept ...
26
+ ```
27
+
28
+ The socket has close-on-exec disabled and is ready to be used in Ruby or passed on to a real service like haproxy via spawn/exec. For an example of using multibinder with haproxy (there are a couple of tricks), see [the haproxy test shim](https://github.com/theojulienne/multibinder/blob/master/test/haproxy_shim.rb).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+ require 'json'
5
+
6
+ class MultiBinderServer
7
+ def initialize(control_file)
8
+ @control_file = control_file
9
+ end
10
+
11
+ def handle_client(s)
12
+ loop do
13
+ msg, _, _, _ = s.recvmsg
14
+ break if msg.empty?
15
+ request = JSON.parse(msg)
16
+ puts "Request: #{request}"
17
+
18
+ case request['method']
19
+ when 'bind'
20
+ do_bind s, request
21
+ else
22
+ response = { :error => { :code => -32601, :message => 'Method not found' } }
23
+ s.sendmsg JSON.dump(response), 0, nil
24
+ end
25
+ end
26
+ end
27
+
28
+ def bind_to_env(bind)
29
+ "MULTIBINDER_BIND__tcp__#{bind['address'].sub('.','_')}__#{bind['port']}"
30
+ end
31
+
32
+ def do_bind(s, request)
33
+ bind = request['params'][0]
34
+
35
+ begin
36
+ name = bind_to_env(bind)
37
+ if ENV[name]
38
+ socket = IO.for_fd ENV[name].to_i
39
+ else
40
+ socket = Socket.new(:INET, :STREAM, 0)
41
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
42
+ socket.fcntl(Fcntl::F_SETFD, socket.fcntl(Fcntl::F_GETFD) & (-Fcntl::FD_CLOEXEC-1))
43
+ socket.bind(Addrinfo.tcp(bind['address'], bind['port']))
44
+ socket.listen(bind['backlog'] || 1000)
45
+ ENV[name] = socket.fileno.to_s
46
+ end
47
+ rescue Exception => e
48
+ response = {
49
+ :jsonrpc => '2.0',
50
+ :id => request['id'],
51
+ :error => {
52
+ :code => 10000,
53
+ :message => "Could not bind: #{e.message}",
54
+ :backtrace => e.backtrace,
55
+ },
56
+ }
57
+ s.sendmsg JSON.dump(response), 0, nil
58
+ return
59
+ else
60
+ response = {
61
+ :jsonrpc => '2.0',
62
+ :id => request['id'],
63
+ :result => true,
64
+ }
65
+ s.sendmsg JSON.dump(response), 0, nil, Socket::AncillaryData.unix_rights(socket)
66
+ end
67
+ end
68
+
69
+ def bind_accept_loop
70
+ UNIXServer.open(@control_file) do |serv|
71
+ puts "Listening for binds on control socket: #{@control_file}"
72
+ STDOUT.flush
73
+
74
+ Signal.trap("USR1") do
75
+ serv.close
76
+ begin
77
+ File.unlink @control_file
78
+ rescue Errno::ENOENT
79
+ # cool!
80
+ end
81
+
82
+ # respawn ourselved in an identical way, keeping state through environment.
83
+ puts 'Respawning...'
84
+ STDOUT.flush
85
+ args = [RbConfig.ruby, $0, *ARGV]
86
+ args << { :close_others => false } if RUBY_VERSION > '1.9' # RUBBY.
87
+ Kernel.exec *args
88
+ end
89
+
90
+ loop do
91
+ s = serv.accept
92
+ begin
93
+ handle_client s
94
+ rescue Exception => e
95
+ puts e
96
+ puts e.backtrace
97
+ ensure
98
+ s.close
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def serve
105
+ begin
106
+ File.unlink @control_file
107
+ puts "Removed existing control socket: #{@control_file}"
108
+ rescue Errno::ENOENT
109
+ # :+1:
110
+ end
111
+
112
+ begin
113
+ bind_accept_loop
114
+ ensure
115
+ begin
116
+ File.unlink @control_file
117
+ rescue Errno::ENOENT
118
+ # cool!
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ if ARGV.length == 0
125
+ abort "Usage: #{$0} <path/to/control.sock>"
126
+ else
127
+ server = MultiBinderServer.new ARGV[0]
128
+ server.serve
129
+ end
@@ -0,0 +1,33 @@
1
+ require 'multibinder/version'
2
+ require 'json'
3
+
4
+ module MultiBinder
5
+ def self.bind(address, port)
6
+ abort 'MULTIBINDER_SOCK environment variable must be set' if !ENV['MULTIBINDER_SOCK']
7
+
8
+ binder = UNIXSocket.open(ENV['MULTIBINDER_SOCK'])
9
+
10
+ # make the request
11
+ binder.sendmsg JSON.dump({
12
+ :jsonrpc => '2.0',
13
+ :method => 'bind',
14
+ :params => [{
15
+ :address => address,
16
+ :port => port,
17
+ }]
18
+ }, 0, nil)
19
+
20
+ # get the response
21
+ msg, _, _, ctl = binder.recvmsg(:scm_rights=>true)
22
+ response = JSON.parse(msg)
23
+ if response['error']
24
+ raise response['error']['message']
25
+ end
26
+
27
+ binder.close
28
+
29
+ socket = ctl.unix_rights[0]
30
+ socket.fcntl(Fcntl::F_SETFD, socket.fcntl(Fcntl::F_GETFD) & (-Fcntl::FD_CLOEXEC-1))
31
+ socket
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module MultiBinder
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'multibinder/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "multibinder"
8
+ spec.version = MultiBinder::VERSION
9
+ spec.authors = ["Theo Julienne"]
10
+ spec.email = ["theojulienne@github.com"]
11
+ spec.summary = %q{multibinder is a tiny ruby server that makes writing zero-downtime-reload services simpler.}
12
+ spec.homepage = "https://github.com/theojulienne/multibinder"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ end
@@ -0,0 +1,13 @@
1
+ #!/bin/sh
2
+
3
+ export RUBY_VERSION=2.1.6-github
4
+ export RBENV_VERSION=2.1.6-github
5
+ export PATH=/usr/share/rbenv/shims:/usr/sbin:$PATH
6
+ export BASE_DIR=/data/binder
7
+
8
+ #bundle install --local --quiet --path vendor/gems
9
+
10
+ REALPATH=$(cd $(dirname "$0") && pwd)
11
+
12
+ cd $REALPATH/../
13
+ script/test
@@ -0,0 +1,15 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ SHELL=/bin/sh
6
+
7
+ date "+%Y-%m-%dT%H:%M:%SZ"
8
+ echo "Beginning tests for multibinder"
9
+ echo "==="
10
+
11
+ ls -1 test/test-*.sh | xargs -I % -n 1 ${SHELL} % --batch
12
+
13
+ echo "==="
14
+ date "+%Y-%m-%dT%H:%M:%SZ"
15
+ echo "Done."
@@ -0,0 +1,27 @@
1
+ require 'multibinder'
2
+
3
+ server = MultiBinder.bind '127.0.0.1', ARGV[0].to_i
4
+
5
+ cfg_fn = "#{ENV['TEMPDIR']}/haproxy.cfg"
6
+
7
+ File.write(cfg_fn, <<eos)
8
+ global
9
+ maxconn 256
10
+
11
+ defaults
12
+ mode http
13
+ timeout connect 5000ms
14
+ timeout client 50000ms
15
+ timeout server 50000ms
16
+
17
+ frontend http-in
18
+ bind fd@#{server.fileno}
19
+ http-request deny
20
+ eos
21
+
22
+ pid = Process.spawn "haproxy", "-f", cfg_fn, :close_others => false
23
+
24
+ Signal.trap("INT") { Process.kill "USR1", pid }
25
+ Signal.trap("TERM") { Process.kill "USR1", pid }
26
+
27
+ Process.waitpid
@@ -0,0 +1,21 @@
1
+ require 'socket'
2
+ require 'json'
3
+ require 'multibinder'
4
+
5
+ server = MultiBinder.bind '127.0.0.1', ARGV[0].to_i
6
+
7
+ loop do
8
+ socket, _ = server.accept
9
+ request = socket.gets
10
+ puts request
11
+
12
+ socket.print "HTTP/1.0 200 OK\r\n"
13
+ socket.print "Content-Type: text/plain\r\n"
14
+ socket.print "Connection: close\r\n"
15
+
16
+ socket.print "\r\n"
17
+
18
+ socket.print "Hello World #{ARGV[1] || ''}!\n"
19
+
20
+ socket.close
21
+ end
@@ -0,0 +1,22 @@
1
+ #!/bin/sh
2
+ # Extends lib.sh for this project
3
+
4
+ project_setup () {
5
+ local control_sock=${TEMPDIR}/multibinder.sock
6
+ launch_service "multibinder" bundle exec multibinder ${control_sock}
7
+
8
+ tries=0
9
+ while [ ! -S $control_sock ]; do
10
+ sleep .1
11
+ echo 'Waiting for control socket...'
12
+ tries=$((tries + 1))
13
+ if [ $tries -gt 10 ]; then
14
+ echo 'Giving up.'
15
+ exit 1
16
+ fi
17
+ done
18
+ }
19
+
20
+ project_cleanup () {
21
+ kill_service "multibinder"
22
+ }
@@ -0,0 +1,282 @@
1
+ #!/bin/sh
2
+ # Usage: . lib.sh
3
+ # Simple shell command language test library.
4
+ #
5
+ # Tests must follow the basic form:
6
+ #
7
+ # begin_test "the thing"
8
+ # (
9
+ # set -e
10
+ # echo "hello"
11
+ # false
12
+ # )
13
+ # end_test
14
+ #
15
+ # When a test fails its stdout and stderr are shown.
16
+ #
17
+ # Note that tests must `set -e' within the subshell block or failed assertions
18
+ # will not cause the test to fail and the result may be misreported.
19
+ #
20
+ # Copyright (c) 2011-13 by Ryan Tomayko <http://tomayko.com>
21
+ # License: MIT
22
+
23
+ set -e
24
+
25
+ TEST_DIR=$(dirname "$0")
26
+ ROLE=$(basename $(dirname "$0"))
27
+ BASE_DIR=$(cd $(dirname "$0")/../ && pwd)
28
+
29
+ TEMPDIR=$(mktemp -d /tmp/test-XXXXXX)
30
+ HOME=$TEMPDIR; export HOME
31
+ TRASHDIR="${TEMPDIR}"
32
+ LOGDIR="$TEMPDIR/log"
33
+
34
+ BUILD_DIR=${TEMPDIR}/build
35
+ ROLE_DIR=$BUILD_DIR
36
+
37
+ # keep track of num tests and failures
38
+ tests=0
39
+ failures=0
40
+
41
+ #mkdir -p $TRASHDIR
42
+ mkdir -p $LOGDIR
43
+
44
+ # offset port numbers if running in '--batch' mode
45
+ TEST_PORT_OFFSET=0
46
+ if [ "$1" = "--batch" ]; then
47
+ TEST_PORT_OFFSET=$(ls -1 $(dirname "$0")/test-*.sh | grep -n $(basename "$0") | grep -o "^[0-9]*")
48
+ TEST_PORT_OFFSET=$(( $TEST_PORT_OFFSET - 1 ))
49
+ fi
50
+
51
+ # Sanity check up front that nothing is currently using the ports we're
52
+ # trying use; port collisions will cause non-obvious test failures.
53
+ tests_use_port () {
54
+ local p=$(offset_port $1)
55
+
56
+ set +e
57
+ lsof -n -iTCP:$p | grep -q LISTEN
58
+ if [ $? -eq 0 ]; then
59
+ echo "**** $(basename "$0") FAIL: Found something using port $p, bailing."
60
+ lsof -n -iTCP:$p | grep -e ":$p" | sed -e "s/^/lsof failure $(basename "$0"): /"
61
+ exit 1
62
+ fi
63
+ set -e
64
+ }
65
+
66
+ # Given a port, increment it by our test number so multiple tests can run
67
+ # in parallel without conflicting.
68
+ offset_port () {
69
+ local base_port="$1"
70
+
71
+ echo $(( $base_port + $TEST_PORT_OFFSET ))
72
+ }
73
+
74
+ # Mark the beginning of a test. A subshell should immediately follow this
75
+ # statement.
76
+ begin_test () {
77
+ test_status=$?
78
+ [ -n "$test_description" ] && end_test $test_status
79
+ unset test_status
80
+
81
+ tests=$(( tests + 1 ))
82
+ test_description="$1"
83
+
84
+ exec 3>&1 4>&2
85
+ out="$TRASHDIR/out"
86
+ err="$TRASHDIR/err"
87
+ exec 1>"$out" 2>"$err"
88
+
89
+ echo "begin_test: $test_description"
90
+
91
+ # allow the subshell to exit non-zero without exiting this process
92
+ set -x +e
93
+ before_time=$(date '+%s')
94
+ }
95
+
96
+ report_failure () {
97
+ msg=$1
98
+ desc=$2
99
+ failures=$(( failures + 1 ))
100
+ printf "test: %-60s $msg\n" "$desc ..."
101
+ (
102
+ echo "-- stdout --"
103
+ sed 's/^/ /' <"$TRASHDIR/out"
104
+ echo "-- stderr --"
105
+ grep -a -v -e '^\+ end_test' -e '^+ set +x' <"$TRASHDIR/err" |
106
+ sed 's/^/ /'
107
+
108
+ for service_log in $(ls $LOGDIR/*.log); do
109
+ echo "-- $(basename "$service_log") --"
110
+ sed 's/^/ /' <"$service_log"
111
+ done
112
+
113
+ echo "-- end --"
114
+ ) 1>&2
115
+ }
116
+
117
+ # Mark the end of a test.
118
+ end_test () {
119
+ test_status="${1:-$?}"
120
+ ex_fail="${2:-0}"
121
+ after_time=$(date '+%s')
122
+ set +x -e
123
+ exec 1>&3 2>&4
124
+ elapsed_time=$((after_time - before_time))
125
+
126
+ if [ "$test_status" -eq 0 ]; then
127
+ if [ "$ex_fail" -eq 0 ]; then
128
+ printf "test: %-60s OK (${elapsed_time}s)\n" "$test_description ..."
129
+ else
130
+ report_failure "OK (unexpected)" "$test_description ..."
131
+ fi
132
+ else
133
+ if [ "$ex_fail" -eq 0 ]; then
134
+ report_failure "FAILED (${elapsed_time}s)" "$test_description ..."
135
+ else
136
+ printf "test: %-60s FAILED (expected)\n" "$test_description ..."
137
+ fi
138
+ fi
139
+ unset test_description
140
+ }
141
+
142
+ # Mark the end of a test that is expected to fail.
143
+ end_test_exfail () {
144
+ end_test $? 1
145
+ }
146
+
147
+ atexit () {
148
+ [ -z "$KEEPTRASH" ] && rm -rf "$TEMPDIR"
149
+ if [ $failures -gt 0 ]; then
150
+ exit 1
151
+ else
152
+ exit 0
153
+ fi
154
+ }
155
+ trap "atexit" EXIT
156
+
157
+ cleanup() {
158
+ set +e
159
+
160
+ project_cleanup "$@"
161
+
162
+ for pid_file in $(ls ${TEMPDIR}/*.pid); do
163
+ echo "Cleaning up process in $pid_file ..."
164
+ kill $(cat ${pid_file}) || true
165
+ done
166
+
167
+ echo "Cleaning up any remaining pid files."
168
+ rm -rf ${TEMPDIR}/*.pid
169
+
170
+ if [ -f "$TEMPDIR/core" ]; then
171
+ echo "found a coredump, failing"
172
+ exit 1
173
+ fi
174
+ }
175
+
176
+ setup() {
177
+ trap cleanup EXIT
178
+ trap cleanup INT
179
+ trap cleanup TERM
180
+
181
+ project_setup "$@"
182
+
183
+ set -e
184
+ }
185
+
186
+ wait_for_file () {
187
+ (
188
+ SERVICE="$1"
189
+ PID_FILE="$2"
190
+
191
+ set +e
192
+
193
+ tries=0
194
+
195
+ echo "Waiting for $SERVICE to drop $PID_FILE"
196
+ while [ ! -e "$PID_FILE" ]; do
197
+ tries=$(( $tries + 1 ))
198
+ if [ $tries -gt 50 ]; then
199
+ echo "FAILED: $SERVICE did not drop $PID_FILE after $tries attempts"
200
+ exit 1
201
+ fi
202
+ echo "Waiting for $SERVICE to drop $PID_FILE"
203
+ sleep 0.1
204
+ done
205
+ echo "OK -- $SERVICE dropped $PID_FILE"
206
+ exit 0
207
+ )
208
+ }
209
+
210
+ # wait for a process to start accepting connections
211
+ wait_for_port () {
212
+ (
213
+ SERVICE="$1"
214
+ SERVICE_PORT="$2"
215
+
216
+ set +e
217
+
218
+ tries=0
219
+
220
+ echo "Waiting for $SERVICE to start accepting connections"
221
+ if [ $(uname) = "Linux" ]; then
222
+ echo "PROXY TCP4 127.0.0.1 127.0.0.1 123 123\r" | nc -q 0 localhost $SERVICE_PORT 2>&1 >/dev/null
223
+ else
224
+ echo "PROXY TCP4 127.0.0.1 127.0.0.1 123 123\r" | nc localhost $SERVICE_PORT 2>&1 >/dev/null
225
+ fi
226
+ while [ $? -ne 0 ]; do
227
+ tries=$(( $tries + 1 ))
228
+ if [ $tries -gt 50 ]; then
229
+ echo "FAILED: $SERVICE not accepting connections after $tries attempts"
230
+ exit 1
231
+ fi
232
+ echo "Waiting for $SERVICE to start accepting connections"
233
+ sleep 0.1
234
+ if [ $(uname) = "Linux" ]; then
235
+ echo "PROXY TCP4 127.0.0.1 127.0.0.1 123 123\r" | nc -q 0 localhost $SERVICE_PORT 2>&1 >/dev/null
236
+ else
237
+ echo "PROXY TCP4 127.0.0.1 127.0.0.1 123 123\r" | nc localhost $SERVICE_PORT 2>&1 >/dev/null
238
+ fi
239
+ done
240
+ echo "OK -- $SERVICE seems to be accepting connections"
241
+ exit 0
242
+ )
243
+ }
244
+
245
+ # Allow simple launching of a background service, keeping track of the pid
246
+ launch_service () {
247
+ local service_name=$1
248
+ shift
249
+
250
+ "$@" >${LOGDIR}/${service_name}.log 2>&1 &
251
+ echo "$!" > ${TEMPDIR}/${service_name}.pid
252
+ }
253
+
254
+ # Clean up after a service launched with launch_service
255
+ kill_service () {
256
+ local service_name=$1
257
+ kill $(cat ${TEMPDIR}/${service_name}.pid) || true
258
+ rm -rf ${TEMPDIR}/${service_name}.pid
259
+ }
260
+
261
+ service_pid () {
262
+ local service_name=$1
263
+ cat ${TEMPDIR}/${service_name}.pid
264
+ }
265
+
266
+ service_log () {
267
+ local service_name=$1
268
+ echo ${LOGDIR}/${service_name}.log
269
+ }
270
+
271
+ # Stub out functions and let the project extend them
272
+ project_setup () {
273
+ true
274
+ }
275
+
276
+ project_cleanup () {
277
+ true
278
+ }
279
+
280
+ if [ -e "$BASE_DIR/test/lib-project.sh" ]; then
281
+ . $BASE_DIR/test/lib-project.sh
282
+ fi
@@ -0,0 +1,29 @@
1
+ #!/bin/sh
2
+ #
3
+ # test-haproxy.sh: check that we can work with haproxy (if it's installed)
4
+
5
+ REALPATH=$(cd $(dirname "$0") && pwd)
6
+ . "${REALPATH}/lib.sh"
7
+
8
+ TEST_PORT=8000
9
+
10
+ tests_use_port $TEST_PORT
11
+
12
+ if ! which -s haproxy; then
13
+ echo "haproxy not available, skipping tests."
14
+ exit 0
15
+ fi
16
+
17
+ begin_test "haproxy runs with multibinder"
18
+ (
19
+ setup
20
+
21
+ export TEMPDIR
22
+
23
+ MULTIBINDER_SOCK=${TEMPDIR}/multibinder.sock launch_service "haproxy" bundle exec ruby test/haproxy_shim.rb $(offset_port $TEST_PORT)
24
+
25
+ wait_for_port "haproxy" $(offset_port $TEST_PORT)
26
+
27
+ curl --max-time 5 http://localhost:$(offset_port $TEST_PORT)/ | grep -q 'Request forbidden'
28
+ )
29
+ end_test
@@ -0,0 +1,99 @@
1
+ #!/bin/sh
2
+ #
3
+ # test-simple.sh: simple sanity checks
4
+
5
+ REALPATH=$(cd $(dirname "$0") && pwd)
6
+ . "${REALPATH}/lib.sh"
7
+
8
+ TEST_PORT=8000
9
+
10
+ tests_use_port $TEST_PORT
11
+
12
+ begin_test "server binds and accepts through multibinder"
13
+ (
14
+ setup
15
+
16
+ MULTIBINDER_SOCK=${TEMPDIR}/multibinder.sock launch_service "http" bundle exec ruby test/httpbinder.rb $(offset_port $TEST_PORT)
17
+
18
+ wait_for_port "binder" $(offset_port $TEST_PORT)
19
+
20
+ curl --max-time 5 http://localhost:$(offset_port $TEST_PORT)/ | grep -q 'Hello World'
21
+ )
22
+ end_test
23
+
24
+ begin_test "server can restart without requests failing while down"
25
+ (
26
+ setup
27
+
28
+ MULTIBINDER_SOCK=${TEMPDIR}/multibinder.sock launch_service "http" bundle exec ruby test/httpbinder.rb $(offset_port $TEST_PORT)
29
+
30
+ wait_for_port "binder" $(offset_port $TEST_PORT)
31
+
32
+ kill_service "http"
33
+
34
+ curl --max-time 5 http://localhost:$(offset_port $TEST_PORT)/ | grep -q 'Hello World' &
35
+ curl_pid=$!
36
+
37
+ sleep 0.5
38
+
39
+ # now restart the service
40
+ MULTIBINDER_SOCK=${TEMPDIR}/multibinder.sock launch_service "http" bundle exec ruby test/httpbinder.rb $(offset_port $TEST_PORT)
41
+
42
+ # curl should finish, and succeed
43
+ wait $curl_pid
44
+ )
45
+ end_test
46
+
47
+
48
+ begin_test "server can load a second copy then terminate the first"
49
+ (
50
+ setup
51
+
52
+ MULTIBINDER_SOCK=${TEMPDIR}/multibinder.sock launch_service "http" bundle exec ruby test/httpbinder.rb $(offset_port $TEST_PORT) "first"
53
+ wait_for_port "binder" $(offset_port $TEST_PORT)
54
+
55
+ curl --max-time 5 http://localhost:$(offset_port $TEST_PORT)/r1 | grep -q 'Hello World first'
56
+
57
+ MULTIBINDER_SOCK=${TEMPDIR}/multibinder.sock launch_service "http2" bundle exec ruby test/httpbinder.rb $(offset_port $TEST_PORT) "second"
58
+
59
+ curl --max-time 5 http://localhost:$(offset_port $TEST_PORT)/r2 | egrep -q 'Hello World (first|second)'
60
+
61
+ kill_service "http"
62
+
63
+ curl --max-time 5 http://localhost:$(offset_port $TEST_PORT)/r3 | grep -q 'Hello World second'
64
+
65
+ kill_service "http2"
66
+ )
67
+ end_test
68
+
69
+ begin_test "multibinder restarts safely on sigusr1"
70
+ (
71
+ setup
72
+
73
+ MULTIBINDER_SOCK=${TEMPDIR}/multibinder.sock launch_service "http" bundle exec ruby test/httpbinder.rb $(offset_port $TEST_PORT)
74
+ wait_for_port "binder" $(offset_port $TEST_PORT)
75
+
76
+ curl --max-time 5 http://localhost:$(offset_port $TEST_PORT)/r1 | grep -q 'Hello World'
77
+
78
+ kill_service "http"
79
+
80
+ lsof -p $(service_pid "multibinder")
81
+
82
+ kill -USR1 $(service_pid "multibinder")
83
+
84
+ # should still be running, should still be listening
85
+ lsof -p $(service_pid "multibinder")
86
+ lsof -i :$(offset_port $TEST_PORT) -a -p $(service_pid "multibinder")
87
+
88
+ # should be able to request the bind again
89
+ MULTIBINDER_SOCK=${TEMPDIR}/multibinder.sock launch_service "http" bundle exec ruby test/httpbinder.rb $(offset_port $TEST_PORT)
90
+ wait_for_port "multibinder" $(offset_port $TEST_PORT)
91
+
92
+ # requests should work
93
+ curl --max-time 5 http://localhost:$(offset_port $TEST_PORT)/r2 | grep -q 'Hello World'
94
+
95
+ # and multibinder should have started listening on the control socket twice
96
+ grep 'Respawning' $(service_log "multibinder")
97
+ grep 'Listening for binds' $(service_log "multibinder") | grep -n 'Listen' | grep "2:"
98
+ )
99
+ end_test
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multibinder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Theo Julienne
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description:
42
+ email:
43
+ - theojulienne@github.com
44
+ executables:
45
+ - multibinder
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - bin/multibinder
55
+ - lib/multibinder.rb
56
+ - lib/multibinder/version.rb
57
+ - multibinder.gemspec
58
+ - script/cibuild
59
+ - script/test
60
+ - test/haproxy_shim.rb
61
+ - test/httpbinder.rb
62
+ - test/lib-project.sh
63
+ - test/lib.sh
64
+ - test/test-haproxy.sh
65
+ - test/test-simple.sh
66
+ homepage: https://github.com/theojulienne/multibinder
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.2.3
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: multibinder is a tiny ruby server that makes writing zero-downtime-reload
90
+ services simpler.
91
+ test_files:
92
+ - test/haproxy_shim.rb
93
+ - test/httpbinder.rb
94
+ - test/lib-project.sh
95
+ - test/lib.sh
96
+ - test/test-haproxy.sh
97
+ - test/test-simple.sh
98
+ has_rdoc: