multibinder 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: