synapse-nginx 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +52 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +87 -0
- data/Rakefile +7 -0
- data/lib/synapse/config_generator/nginx.rb +293 -0
- data/lib/synapse-nginx.rb +6 -0
- data/spec/lib/synapse/nginx_spec.rb +95 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/configuration.rb +7 -0
- data/spec/support/minimum.conf.yaml +26 -0
- data/synapse-nginx.gemspec +24 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7a5454a83b1bd3322bf0c9b7f558ee981fb62b95
|
4
|
+
data.tar.gz: ab54119636b02cfe38d461a80a00dab1922ca339
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c5def2cb862ce7c5a833e0fa000db8a359e2536162cb5a66f7d605a06d5abeddc105e0cd4115af726938a1053ddbc12c65b0a6682f4c6c0bd1ff5c2fb0f058f
|
7
|
+
data.tar.gz: 3b296dad82cebe5e15f5896162d3cd8365f86af6dce0ea36448d78c919a347d8eaa9fb09280299a0e06b3470aa5ca19dc82a112ee5d0104d2e3d85d49667079c
|
data/.gitignore
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
Gemfile.lock
|
46
|
+
.ruby-version
|
47
|
+
.ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
51
|
+
|
52
|
+
.*sw?
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Joseph Lynch
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# synapse-nginx
|
2
|
+
A config_generator for the Synapse framework that supports NGINX.
|
3
|
+
|
4
|
+
## Installation ##
|
5
|
+
To install this plugin for Synapse, gem install it after [`synapse`](https://github.com/airbnb/synapse/)
|
6
|
+
in your packaging setup.
|
7
|
+
|
8
|
+
```bash
|
9
|
+
$ gem install synapse --install-dir /opt/smartstack/synapse --no-ri --no-rdoc
|
10
|
+
$ gem install synapse-nginx --install-dir /opt/smartstack/synapse --no-ri --no-rdoc
|
11
|
+
```
|
12
|
+
|
13
|
+
## Configuration ##
|
14
|
+
The NGINX plugin slots right into the standard [`synapse`](https://github.com/airbnb/synapse/)
|
15
|
+
configuration file.
|
16
|
+
|
17
|
+
### Top level config ###
|
18
|
+
The NGINX plugin requires a section to be added to the top level of your
|
19
|
+
Synapse [config](https://github.com/airbnb/synapse#configuration). For example:
|
20
|
+
|
21
|
+
```yaml
|
22
|
+
haproxy:
|
23
|
+
# old config for haproxy
|
24
|
+
nginx:
|
25
|
+
contexts:
|
26
|
+
main: [
|
27
|
+
'worker_processes 1',
|
28
|
+
'pid /tmp/nginx.pid'
|
29
|
+
]
|
30
|
+
events: [
|
31
|
+
'worker_connections 1024'
|
32
|
+
]
|
33
|
+
do_writes: false
|
34
|
+
do_reloads: false
|
35
|
+
```
|
36
|
+
|
37
|
+
The top level `nginx` section of the config file. If provided, it must have
|
38
|
+
a `contexts` top level key which contains a hash with the following two
|
39
|
+
contexts defined:
|
40
|
+
|
41
|
+
* `main`: A list of configuration lines to add to the top level of the nginx
|
42
|
+
configuration. Typically this contains things like pid files or worker process countes.
|
43
|
+
* `events`: A list of configuration lines to add to the events section of the
|
44
|
+
nginx configuration. Typically this contians things like worker_connection counts.
|
45
|
+
|
46
|
+
The following options may be provided to control how nginx writes config:
|
47
|
+
* `do_writes`: whether or not the config file will be written (default to `true`)
|
48
|
+
* `config_file_path`: where Synapse will write the nginx config file. Required if `do_writes` is set.
|
49
|
+
* `check_command`: the command Synapse will run to ensure the generated NGINX config is valid before reloading.
|
50
|
+
Required if `do_writes` is set.
|
51
|
+
|
52
|
+
You can control reloading with:
|
53
|
+
* `do_reloads`: whether or not Synapse will reload nginx automatically (default to `true`)
|
54
|
+
* `reload_command`: the command Synapse will run to reload NGINX. Required if `do_reloads` is set.
|
55
|
+
* `start_command`: the command Synapse will run to start NGINX the first time. Required if `do_reloads` is set.
|
56
|
+
* `restart_interval`: number of seconds to wait between restarts of NGINX (default: 2)
|
57
|
+
* `restart_jitter`: percentage, expressed as a float, of jitter to multiply the `restart_interval` by when determining the next
|
58
|
+
restart time. Use this to help prevent storms when HAProxy restarts. (default: 0.0)
|
59
|
+
|
60
|
+
You can also provide:
|
61
|
+
* `listen_address`: force NGINX to listen on this address (default: localhost)
|
62
|
+
|
63
|
+
Note that a non-default `listen_address` can be dangerous.
|
64
|
+
If you configure an `address:port` combination that is already in use on the system, nginx will fail to start.
|
65
|
+
|
66
|
+
### Service watcher config ###
|
67
|
+
Each service watcher may supply custom configuration that tells Synapse how to
|
68
|
+
configure nginx for just that service.
|
69
|
+
|
70
|
+
This section is its own hash, which can contain the following keys:
|
71
|
+
|
72
|
+
* `disabled`: A boolean value indicating if nginx configuration management
|
73
|
+
for just this service instance ought be disabled. For example, if you want
|
74
|
+
haproxy output for a particular service but not nginx config. (default: `false`)
|
75
|
+
* `mode`: The type of proxy, either `http` or `tcp`. This impacts whether
|
76
|
+
nginx generates a stream or an http backend for this service. (default: `http`)
|
77
|
+
* `upstream`: A list of configuration lines to add to the `upstream` section of
|
78
|
+
nginx. (default: [])
|
79
|
+
* `server`: A list of configuration lines to add to the `server` section of
|
80
|
+
nginx. (default: [])
|
81
|
+
* `listen_address`: force nginx to listen on this address (default is localhost).
|
82
|
+
Setting `listen_address` on a per service basis overrides the global `listen_address`
|
83
|
+
in the top level `nginx` config hash.
|
84
|
+
* `upstream_order`: how servers should be ordered in the `upstream` stanza. Setting to `asc` means sorting backends in ascending alphabetical order before generating stanza. `desc` means descending alphabetical order. `no_shuffle` means no shuffling or sorting. (default: `shuffle`, which results in random ordering of upstream servers)
|
85
|
+
* `upstream_name`: The name of the generated nginx backend for this service
|
86
|
+
(defaults to the service's key in the `services` section)
|
87
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,293 @@
|
|
1
|
+
require 'synapse/config_generator/base'
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
class Synapse::ConfigGenerator
|
7
|
+
class Nginx < BaseGenerator
|
8
|
+
include Synapse::Logging
|
9
|
+
|
10
|
+
NAME = 'nginx'.freeze
|
11
|
+
|
12
|
+
def initialize(opts)
|
13
|
+
%w{main events}.each do |req|
|
14
|
+
if !opts.fetch('contexts', {}).has_key?(req)
|
15
|
+
raise ArgumentError, "nginx requires a contexts.#{req} section"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
@opts = opts
|
20
|
+
@contexts = opts['contexts']
|
21
|
+
@opts['do_writes'] = true unless @opts.key?('do_writes')
|
22
|
+
@opts['do_reloads'] = true unless @opts.key?('do_reloads')
|
23
|
+
|
24
|
+
req_pairs = {
|
25
|
+
'do_writes' => ['config_file_path', 'check_command'],
|
26
|
+
'do_reloads' => ['reload_command', 'start_command'],
|
27
|
+
}
|
28
|
+
|
29
|
+
req_pairs.each do |cond, reqs|
|
30
|
+
if opts[cond]
|
31
|
+
unless reqs.all? {|req| opts[req]}
|
32
|
+
missing = reqs.select {|req| not opts[req]}
|
33
|
+
raise ArgumentError, "the `#{missing}` option(s) are required when `#{cond}` is true"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# how to restart nginx
|
39
|
+
@restart_interval = @opts.fetch('restart_interval', 2).to_i
|
40
|
+
@restart_jitter = @opts.fetch('restart_jitter', 0).to_f
|
41
|
+
@restart_required = true
|
42
|
+
@has_started = false
|
43
|
+
|
44
|
+
# virtual clock bookkeeping for controlling how often nginx restarts
|
45
|
+
@time = 0
|
46
|
+
@next_restart = @time
|
47
|
+
|
48
|
+
# a place to store the parsed nginx config from each watcher
|
49
|
+
@watcher_configs = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
def normalize_watcher_provided_config(service_watcher_name, service_watcher_config)
|
53
|
+
service_watcher_config = super(service_watcher_name, service_watcher_config)
|
54
|
+
defaults = {
|
55
|
+
'mode' => 'http',
|
56
|
+
'upstream' => [],
|
57
|
+
'server' => [],
|
58
|
+
'disabled' => false,
|
59
|
+
}
|
60
|
+
unless service_watcher_config.include?('port')
|
61
|
+
log.warn "synapse: service #{service_watcher_name}: nginx config does not include a port; only upstream sections for the service will be created; you must move traffic there manually using server sections"
|
62
|
+
end
|
63
|
+
defaults.merge(service_watcher_config)
|
64
|
+
end
|
65
|
+
|
66
|
+
def tick(watchers)
|
67
|
+
@time += 1
|
68
|
+
|
69
|
+
# We potentially have to restart if the restart was rate limited
|
70
|
+
# in the original call to update_config
|
71
|
+
restart if opts['do_reloads'] && @restart_required
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_config(watchers)
|
75
|
+
# generate a new config
|
76
|
+
new_config = generate_config(watchers)
|
77
|
+
|
78
|
+
# if we write config files, lets do that and then possibly restart
|
79
|
+
if opts['do_writes']
|
80
|
+
@restart_required = write_config(new_config)
|
81
|
+
restart if opts['do_reloads'] && @restart_required
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# generates a new config based on the state of the watchers
|
86
|
+
def generate_config(watchers)
|
87
|
+
new_config = generate_base_config
|
88
|
+
|
89
|
+
http, stream = [], []
|
90
|
+
watchers.each do |watcher|
|
91
|
+
watcher_config = watcher.config_for_generator[name]
|
92
|
+
next if watcher_config['disabled']
|
93
|
+
# There seems to be no way to have empty TCP listeners ... just
|
94
|
+
# don't bind the port at all? ... idk
|
95
|
+
next if watcher_config['mode'] == 'tcp' && watcher.backends.empty?
|
96
|
+
|
97
|
+
section = case watcher_config['mode']
|
98
|
+
when 'http'
|
99
|
+
http
|
100
|
+
when 'tcp'
|
101
|
+
stream
|
102
|
+
else
|
103
|
+
raise ArgumentError, "synapse does not understand #{watcher_config['mode']} as a service mode"
|
104
|
+
end
|
105
|
+
section << generate_server(watcher).flatten
|
106
|
+
section << generate_upstream(watcher).flatten
|
107
|
+
end
|
108
|
+
|
109
|
+
unless http.empty?
|
110
|
+
new_config << 'http {'
|
111
|
+
new_config.concat(http.flatten)
|
112
|
+
new_config << "}\n"
|
113
|
+
end
|
114
|
+
|
115
|
+
unless stream.empty?
|
116
|
+
new_config << 'stream {'
|
117
|
+
new_config.concat(stream.flatten)
|
118
|
+
new_config << "}\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
log.debug "synapse: new nginx config: #{new_config}"
|
122
|
+
return new_config.flatten.join("\n")
|
123
|
+
end
|
124
|
+
|
125
|
+
# generates the global and defaults sections of the config file
|
126
|
+
def generate_base_config
|
127
|
+
base_config = ["# auto-generated by synapse at #{Time.now}\n"]
|
128
|
+
|
129
|
+
# The "main" context is special and is the top level
|
130
|
+
@contexts['main'].each do |option|
|
131
|
+
base_config << "#{option};"
|
132
|
+
end
|
133
|
+
base_config << "\n"
|
134
|
+
|
135
|
+
# http and streams are generated separately
|
136
|
+
@contexts.keys.select{|key| !(["main", "http", "stream"].include?(key))}.each do |context|
|
137
|
+
base_config << "#{context} {"
|
138
|
+
@contexts[context].each do |option|
|
139
|
+
base_config << "\t#{option};"
|
140
|
+
end
|
141
|
+
base_config << "}\n"
|
142
|
+
end
|
143
|
+
return base_config
|
144
|
+
end
|
145
|
+
|
146
|
+
def generate_server(watcher)
|
147
|
+
watcher_config = watcher.config_for_generator[name]
|
148
|
+
unless watcher_config.has_key?('port')
|
149
|
+
log.debug "synapse: not generating server stanza for watcher #{watcher.name} because it has no port defined"
|
150
|
+
return []
|
151
|
+
else
|
152
|
+
port = watcher_config['port']
|
153
|
+
end
|
154
|
+
|
155
|
+
listen_address = (
|
156
|
+
watcher_config['listen_address'] ||
|
157
|
+
opts['listen_address'] ||
|
158
|
+
'localhost'
|
159
|
+
)
|
160
|
+
upstream_name = watcher_config.fetch('upstream_name', watcher.name)
|
161
|
+
|
162
|
+
stanza = [
|
163
|
+
"\tserver {",
|
164
|
+
"\t\tlisten #{listen_address}:#{port};",
|
165
|
+
watcher_config['server'].map {|c| "\t\t#{c};"},
|
166
|
+
generate_proxy(watcher_config['mode'], upstream_name, watcher.backends.empty?),
|
167
|
+
"\t}",
|
168
|
+
]
|
169
|
+
end
|
170
|
+
|
171
|
+
# Nginx has some annoying differences between how upstreams in the
|
172
|
+
# http (http) module and the stream (tcp) module address upstreams
|
173
|
+
def generate_proxy(mode, upstream_name, empty_upstream)
|
174
|
+
upstream_name = "http://#{upstream_name}" if mode == 'http'
|
175
|
+
|
176
|
+
case mode
|
177
|
+
when 'http'
|
178
|
+
if empty_upstream
|
179
|
+
value = "\t\t\treturn 503;"
|
180
|
+
else
|
181
|
+
value = "\t\t\tproxy_pass #{upstream_name};"
|
182
|
+
end
|
183
|
+
stanza = [
|
184
|
+
"\t\tlocation / {",
|
185
|
+
value,
|
186
|
+
"\t\t}"
|
187
|
+
]
|
188
|
+
when 'tcp'
|
189
|
+
stanza = [
|
190
|
+
"\t\tproxy_pass #{upstream_name};",
|
191
|
+
]
|
192
|
+
else
|
193
|
+
[]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def generate_upstream(watcher)
|
198
|
+
backends = {}
|
199
|
+
watcher_config = watcher.config_for_generator[name]
|
200
|
+
upstream_name = watcher_config.fetch('upstream_name', watcher.name)
|
201
|
+
|
202
|
+
watcher.backends.each {|b| backends[construct_name(b)] = b}
|
203
|
+
|
204
|
+
# nginx doesn't like upstreams with no backends?
|
205
|
+
return [] if backends.empty?
|
206
|
+
|
207
|
+
keys = case watcher_config['upstream_order']
|
208
|
+
when 'asc'
|
209
|
+
backends.keys.sort
|
210
|
+
when 'desc'
|
211
|
+
backends.keys.sort.reverse
|
212
|
+
when 'no_shuffle'
|
213
|
+
backends.keys
|
214
|
+
else
|
215
|
+
backends.keys.shuffle
|
216
|
+
end
|
217
|
+
|
218
|
+
stanza = [
|
219
|
+
"\tupstream #{upstream_name} {",
|
220
|
+
watcher_config['upstream'].map {|c| "\t\t#{c};"},
|
221
|
+
keys.map {|backend_name|
|
222
|
+
backend = backends[backend_name]
|
223
|
+
b = "\t\tserver #{backend['host']}:#{backend['port']}"
|
224
|
+
b = "#{b} #{watcher_config['server_options']}" if watcher_config['server_options']
|
225
|
+
"#{b};"
|
226
|
+
},
|
227
|
+
"\t}"
|
228
|
+
]
|
229
|
+
end
|
230
|
+
|
231
|
+
# writes the config
|
232
|
+
def write_config(new_config)
|
233
|
+
begin
|
234
|
+
old_config = File.read(opts['config_file_path'])
|
235
|
+
rescue Errno::ENOENT => e
|
236
|
+
log.info "synapse: could not open nginx config file at #{opts['config_file_path']}"
|
237
|
+
old_config = ""
|
238
|
+
end
|
239
|
+
|
240
|
+
if old_config == new_config
|
241
|
+
return false
|
242
|
+
else
|
243
|
+
File.open(opts['config_file_path'],'w') {|f| f.write(new_config)}
|
244
|
+
check = `#{opts['check_command']}`.chomp
|
245
|
+
unless $?.success?
|
246
|
+
log.error "synapse: nginx configuration is invalid according to #{opts['check_command']}!"
|
247
|
+
log.error 'synapse: not restarting nginx as a result'
|
248
|
+
return false
|
249
|
+
end
|
250
|
+
|
251
|
+
return true
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# restarts nginx if the time is right
|
256
|
+
def restart
|
257
|
+
if @time < @next_restart
|
258
|
+
log.info "synapse: at time #{@time} waiting until #{@next_restart} to restart"
|
259
|
+
return
|
260
|
+
end
|
261
|
+
|
262
|
+
@next_restart = @time + @restart_interval
|
263
|
+
@next_restart += rand(@restart_jitter * @restart_interval + 1)
|
264
|
+
|
265
|
+
# do the actual restart
|
266
|
+
unless @has_started
|
267
|
+
log.info "synapse: attempting to run #{opts['start_command']} to get nginx started"
|
268
|
+
log.info 'synapse: this can fail if nginx is already running'
|
269
|
+
res = `#{opts['start_command']}`.chomp
|
270
|
+
@has_started = true
|
271
|
+
end
|
272
|
+
|
273
|
+
res = `#{opts['reload_command']}`.chomp
|
274
|
+
unless $?.success?
|
275
|
+
log.error "failed to reload nginx via #{opts['reload_command']}: #{res}"
|
276
|
+
return
|
277
|
+
end
|
278
|
+
log.info "synapse: restarted nginx"
|
279
|
+
|
280
|
+
@restart_required = false
|
281
|
+
end
|
282
|
+
|
283
|
+
# used to build unique, consistent nginx names for backends
|
284
|
+
def construct_name(backend)
|
285
|
+
name = "#{backend['host']}:#{backend['port']}"
|
286
|
+
if backend['name'] && !backend['name'].empty?
|
287
|
+
name = "#{backend['name']}_#{name}"
|
288
|
+
end
|
289
|
+
|
290
|
+
return name
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'synapse/config_generator/nginx'
|
3
|
+
require 'synapse/service_watcher'
|
4
|
+
|
5
|
+
class MockWatcher; end;
|
6
|
+
|
7
|
+
describe Synapse::ConfigGenerator::Nginx do
|
8
|
+
subject { Synapse::ConfigGenerator::Nginx.new(config['nginx']) }
|
9
|
+
|
10
|
+
let(:mockwatcher) do
|
11
|
+
mockWatcher = double(Synapse::ServiceWatcher)
|
12
|
+
allow(mockWatcher).to receive(:name).and_return('example_service')
|
13
|
+
backends = [{ 'host' => 'somehost', 'port' => 5555}]
|
14
|
+
allow(mockWatcher).to receive(:backends).and_return(backends)
|
15
|
+
watcher_config = subject.normalize_watcher_provided_config('example_service', {'port' => 2199})
|
16
|
+
allow(mockWatcher).to receive(:config_for_generator).and_return({
|
17
|
+
'nginx' => watcher_config
|
18
|
+
})
|
19
|
+
mockWatcher
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:mockwatcher_disabled) do
|
23
|
+
mockWatcher = double(Synapse::ServiceWatcher)
|
24
|
+
allow(mockWatcher).to receive(:name).and_return('disabled_watcher')
|
25
|
+
backends = [{ 'host' => 'somehost', 'port' => 5555}]
|
26
|
+
allow(mockWatcher).to receive(:backends).and_return(backends)
|
27
|
+
watcher_config = subject.normalize_watcher_provided_config('disabled_watcher', {'port' => 2199, 'disabled' => true})
|
28
|
+
allow(mockWatcher).to receive(:config_for_generator).and_return({
|
29
|
+
'nginx' => watcher_config
|
30
|
+
})
|
31
|
+
mockWatcher
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'validates arguments' do
|
35
|
+
it 'succeeds on minimal config' do
|
36
|
+
expect{Synapse::ConfigGenerator::Nginx.new(config['nginx'])}.not_to raise_error
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'validates req_pairs' do
|
40
|
+
req_pairs = {
|
41
|
+
'do_writes' => ['config_file_path', 'check_command'],
|
42
|
+
'do_reloads' => ['reload_command', 'start_command'],
|
43
|
+
}
|
44
|
+
valid_conf = {
|
45
|
+
'contexts' => {'main' => [], 'events' => []},
|
46
|
+
'do_writes' => false,
|
47
|
+
'do_reloads' => false
|
48
|
+
}
|
49
|
+
|
50
|
+
req_pairs.each do |key, value|
|
51
|
+
conf = valid_conf.clone
|
52
|
+
conf[key] = true
|
53
|
+
expect{Synapse::ConfigGenerator::Nginx.new(conf)}.
|
54
|
+
to raise_error(ArgumentError, "the `#{value}` option(s) are required when `#{key}` is true")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'properly defaults do_writes, do_reloads' do
|
59
|
+
conf = {
|
60
|
+
'contexts' => {'main' => [], 'events' => []},
|
61
|
+
'config_file_path' => 'test_file',
|
62
|
+
'reload_command' => 'test_reload',
|
63
|
+
'start_command' => 'test_start',
|
64
|
+
'check_command' => 'test_check'
|
65
|
+
}
|
66
|
+
expect{Synapse::ConfigGenerator::Nginx.new(conf)}.not_to raise_error
|
67
|
+
nginx = Synapse::ConfigGenerator::Nginx.new(conf)
|
68
|
+
expect(nginx.instance_variable_get(:@opts)['do_writes']).to eql(true)
|
69
|
+
expect(nginx.instance_variable_get(:@opts)['do_reloads']).to eql(true)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'complains when main or events are not passed at all' do
|
73
|
+
conf = {
|
74
|
+
'contexts' => {}
|
75
|
+
}
|
76
|
+
expect{Synapse::ConfigGenerator::Nginx.new(conf)}.to raise_error(ArgumentError)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#name' do
|
81
|
+
it 'returns nginx' do
|
82
|
+
expect(subject.name).to eq('nginx')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'disabled watcher' do
|
87
|
+
let(:watchers) { [mockwatcher, mockwatcher_disabled] }
|
88
|
+
|
89
|
+
it 'does not generate config' do
|
90
|
+
expect(subject).to receive(:generate_server).exactly(:once).with(mockwatcher).and_return([])
|
91
|
+
expect(subject).to receive(:generate_upstream).exactly(:once).with(mockwatcher).and_return([])
|
92
|
+
subject.update_config(watchers)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
require "#{File.dirname(__FILE__)}/../lib/synapse-nginx"
|
8
|
+
require 'support/configuration'
|
9
|
+
|
10
|
+
# general RSpec config
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run :focus
|
14
|
+
config.include Configuration
|
15
|
+
|
16
|
+
# verify every double we can think of
|
17
|
+
config.mock_with :rspec do |mocks|
|
18
|
+
mocks.verify_doubled_constant_names = true
|
19
|
+
mocks.verify_partial_doubles = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Run specs in random order to surface order dependencies. If you find an
|
23
|
+
# order dependency and want to debug it, you can fix the order by providing
|
24
|
+
# the seed, which is printed after each run.
|
25
|
+
# --seed 1234
|
26
|
+
config.order = 'random'
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# list the services to connect
|
2
|
+
services:
|
3
|
+
test:
|
4
|
+
default_servers:
|
5
|
+
- { name: default1, host: localhost, port: 8080}
|
6
|
+
discovery:
|
7
|
+
method: zookeeper
|
8
|
+
path: /airbnb/service/logging/event_collector
|
9
|
+
hosts:
|
10
|
+
- localhost:2181
|
11
|
+
nginx:
|
12
|
+
port: 3219
|
13
|
+
bind_address: 'localhost'
|
14
|
+
|
15
|
+
# settings for nginx
|
16
|
+
nginx:
|
17
|
+
contexts:
|
18
|
+
main: [
|
19
|
+
'worker_processes 1',
|
20
|
+
'pid /tmp/nginx.pid'
|
21
|
+
]
|
22
|
+
events: [
|
23
|
+
'worker_connections 1024'
|
24
|
+
]
|
25
|
+
do_writes: false
|
26
|
+
do_reloads: false
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'synapse-nginx'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "synapse-nginx"
|
8
|
+
gem.version = Synapse::Nginx::VERSION
|
9
|
+
gem.authors = ["Joseph Lynch"]
|
10
|
+
gem.email = ["jlynch@yelp.com"]
|
11
|
+
gem.description = "Nginx config_generator for Synapse"
|
12
|
+
gem.licenses = ['MIT']
|
13
|
+
gem.summary = %q{Dynamic NGINX configuration plugin for Synapse}
|
14
|
+
gem.homepage = "https://github.com/jolynch/synapse-nginx"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
|
20
|
+
gem.add_runtime_dependency "synapse", "~> 0.14"
|
21
|
+
|
22
|
+
gem.add_development_dependency "rake", "~> 0"
|
23
|
+
gem.add_development_dependency "rspec", "~> 3.1"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: synapse-nginx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joseph Lynch
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: synapse
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.14'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.1'
|
55
|
+
description: Nginx config_generator for Synapse
|
56
|
+
email:
|
57
|
+
- jlynch@yelp.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/synapse-nginx.rb
|
69
|
+
- lib/synapse/config_generator/nginx.rb
|
70
|
+
- spec/lib/synapse/nginx_spec.rb
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
- spec/support/configuration.rb
|
73
|
+
- spec/support/minimum.conf.yaml
|
74
|
+
- synapse-nginx.gemspec
|
75
|
+
homepage: https://github.com/jolynch/synapse-nginx
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.2.2
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Dynamic NGINX configuration plugin for Synapse
|
99
|
+
test_files:
|
100
|
+
- spec/lib/synapse/nginx_spec.rb
|
101
|
+
- spec/spec_helper.rb
|
102
|
+
- spec/support/configuration.rb
|
103
|
+
- spec/support/minimum.conf.yaml
|