synapse 0.0.1 → 0.2.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.
data/.gitignore CHANGED
@@ -15,3 +15,6 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ *~
19
+ .vagrant
20
+ .*sw?
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ synapse (0.1.4)
5
+ thrift (~> 0.9.0)
6
+ zk (~> 1.7.4)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ coderay (1.0.8)
12
+ diff-lcs (1.1.3)
13
+ little-plugger (1.1.3)
14
+ logging (1.7.2)
15
+ little-plugger (>= 1.1.3)
16
+ method_source (0.8.1)
17
+ pry (0.9.10)
18
+ coderay (~> 1.0.5)
19
+ method_source (~> 0.8)
20
+ slop (~> 3.3.1)
21
+ pry-nav (0.2.2)
22
+ pry (~> 0.9.10)
23
+ rspec (2.12.0)
24
+ rspec-core (~> 2.12.0)
25
+ rspec-expectations (~> 2.12.0)
26
+ rspec-mocks (~> 2.12.0)
27
+ rspec-core (2.12.0)
28
+ rspec-expectations (2.12.0)
29
+ diff-lcs (~> 1.1.3)
30
+ rspec-mocks (2.12.0)
31
+ slop (3.3.3)
32
+ thrift (0.9.0)
33
+ zk (1.7.5)
34
+ logging (~> 1.7.2)
35
+ zookeeper (~> 1.4.0)
36
+ zookeeper (1.4.4)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ pry
43
+ pry-nav
44
+ rspec
45
+ synapse!
data/README.md CHANGED
@@ -1,6 +1,95 @@
1
- # Synapse
1
+ # Synapse #
2
2
 
3
- Write a gem description
3
+ Synapse is Airbnb's new system for service discovery.
4
+ Synapse solves the problem of automated fail-over in the cloud, where failover via network re-configuration is impossible.
5
+ The end result is the ability to connect internal services together in a scalable, fault-tolerant way.
6
+
7
+ ## Motivation ##
8
+
9
+ Synapse emerged from the need to maintain high-availability applications in the cloud.
10
+ Traditional high-availability techniques, which involve using a CRM like [pacemaker](http://linux-ha.org/wiki/Pacemaker), do not work in environments where the end-user has no control over the networking.
11
+ In an environment like Amazon's EC2, all of the available workarounds are suboptimal:
12
+
13
+ * Round-robin DNS: Slow to converge, and doesn't work when applications cache DNS lookups (which is frequent)
14
+ * Elastic IPs: slow to converge, limited in number, public-facing-only, which makes them less useful for internal services
15
+ * ELB: Again, public-facing only, and only useful for HTTP
16
+
17
+ One solution to this problem is a discovery service, like [Apache Zookeeper](http://zookeeper.apache.org/).
18
+ However, Zookeeper and similar services have their own problems:
19
+
20
+ * Service discovery is embedded in all of your apps; often, integration is not simple
21
+ * The discovery layer itself it subject to failure
22
+ * Requires additional servers/instances
23
+
24
+ Synapse solves these difficulties in a simple and fault-tolerant way.
25
+
26
+ ## How Synapse Works ##
27
+
28
+ Synapse runs on your application servers; here at Airbnb, we just run it on every box we deploy.
29
+ The heart of synapse is actually [HAProxy](http://haproxy.1wt.eu/), a stable and proven routing component.
30
+ For every external service that your application talks to, we assign a synapse local port on localhost.
31
+ Synapse creates a proxy from the local port to the service, and you reconfigure your application to talk to the proxy.
32
+
33
+ Synapse comes with a number of `watchers`, which are responsible for service discovery.
34
+ The synapse watchers take care of re-configuring the proxy so that it always points at available servers.
35
+ We've included a number of default watchers, including ones that query zookeeper and ones using the AWS API.
36
+ It is easy to write your own watchers for your use case, and we encourage submitting them back to the project.
37
+
38
+ ## Example Migration ##
39
+
40
+ Lets suppose your rails application depends on a Postgre database instance.
41
+ The database.yaml file has the DB host and port hardcoded:
42
+
43
+ ```yaml
44
+ production:
45
+ database: mydb
46
+ host: mydb.example.com
47
+ port: 5432
48
+ ```
49
+
50
+ You would like to be able to fail over to a different database in case the original dies.
51
+ Let's suppose your instance is running in AWS and you're using the tag 'proddb' set to 'true' to indicate the prod DB.
52
+ You set up synapse to proxy the DB connection on `localhost:3219` in the `synapse.conf.json` file.
53
+ Add a hash under `services` that looks like this:
54
+
55
+ ```json
56
+ {"services":
57
+ {
58
+ "name": "proddb",
59
+ "local_port": 3219,
60
+ "server_options": "check inter 2000 rise 3 fall 2",
61
+ "default_servers": [
62
+ {
63
+ "name": "default-db",
64
+ "host": "mydb.example.com",
65
+ "port": 5432
66
+ }
67
+ ],
68
+ "discovery": {
69
+ "method": "awstag",
70
+ "tag": "proddb",
71
+ "value": "true"
72
+ },
73
+ "listen": [
74
+ ]
75
+ },
76
+ ...
77
+ ```
78
+
79
+ And then change your database.yaml file to look like this:
80
+
81
+ ```yaml
82
+ production:
83
+ database: mydb
84
+ host: localhost
85
+ port: 3219
86
+ ```
87
+
88
+ Start up synapse.
89
+ It will configure HAProxy with a proxy from `localhost:3219` to your DB.
90
+ It will attempt to find the DB using the AWS API; if that does not work, it will default to the DB given in `default_servers`.
91
+ In the worst case, if AWS API is down and you need to change which DB your application talks to, simply edit the `synapse.conf.json` file, update the `default_servers` and restart synapse.
92
+ HAProxy will be transparently reloaded, and your application will keep running without a hiccup.
4
93
 
5
94
  ## Installation
6
95
 
@@ -16,9 +105,73 @@ Or install it yourself as:
16
105
 
17
106
  $ gem install synapse
18
107
 
19
- ## Usage
108
+ ## Configuration ##
109
+
110
+ Synapse depends on a single config file in JSON format; it's usually called `synapse.conf.json`.
111
+ The file has two main sections.
112
+ The first is the `services` section, which lists the services you'd like to connect.
113
+ The second is the `haproxy` section, which specifies how to configure and interact with HAProxy.
114
+
115
+ ### Configuring a Service ###
116
+
117
+ Each service hash has the following options:
118
+
119
+ * `name`: a human-readable name for the service, this is used in logs and notifications
120
+ * `local_port`: the port (on localhost) where HAProxy will listen for connections to the serivce
121
+ * `discovery`: how synapse will discover hosts providing this service (see below)
122
+ * `default_servers`: the list of default servers providing this service; synapse uses these if none others can be discovered
123
+ * `server_port_override`: the port that discovered servers listen on; if the discovery method discovers a port along with hostnames (like the zookeeper watcher) this option may be left out, but will be used in preference if given
124
+ * `server_options`: the haproxy options for each `server` line of the service in HAProxy config; may be left out
125
+ * `listen`: additional lines passed to the HAProxy config in the `listen` stanza of this service
20
126
 
21
- Write usage instructions here
127
+ #### Service Discovery ####
128
+
129
+ We've included a number of `watchers` which provide service discovery.
130
+ Put these into the `discovery` section of the service hash, with these options:
131
+
132
+ ##### Stub #####
133
+
134
+ The stub watcher, this is useful in situations where you only want to use the servers in the `default_servers` list.
135
+ It has only one option:
136
+
137
+ * `method`: stub
138
+
139
+ ##### Zookeeper #####
140
+
141
+ This watcher retrieves a list of servers from zookeeper.
142
+ It takes the following options:
143
+
144
+ * `method`: zookeeper
145
+ * `path`: the zookeeper path where ephemeral nodes will be created for each available service server
146
+ * `hosts`: the list of zookeeper servers to query
147
+
148
+ The watcher assumes that each node under `path` represents a service server.
149
+ Synapse attempts to decode the data in each of these nodes using JSON and also using Thrift under the standard Twitter service encoding.
150
+ We assume that the data contains a hostname and a port for service servers.
151
+
152
+ #### Listing Default Servers ####
153
+
154
+ You may list a number of default servers providing a service.
155
+ Each hash in that section has the following options:
156
+
157
+ * `name`: a human-readable name for the default server; must be unique
158
+ * `host`: the host or IP address of the server
159
+ * `port`: the port where the service runs on the `host`
160
+
161
+ The `default_servers` list is used only when service discovery returns no servers.
162
+ In that case, the service proxy will be created with the servers listed here.
163
+ If you do not list any default servers, no proxy will be created.
164
+
165
+ ### Configuring HAProxy ###
166
+
167
+ The `haproxy` section of the config file has the following options:
168
+
169
+ * `reload_command`: the command Synapse will run to reload HAProxy
170
+ * `config_file_path`: where Synapse will write the HAProxy config file
171
+ * `do_writes`: whether or not the config file will be written (default to `true`)
172
+ * `do_reloads`: whether or not Synapse will reload HAProxy (default to `true`)
173
+ * `global`: options listed here will be written into the `global` section of the HAProxy config
174
+ * `defaults`: options listed here will be written into the `defaults` section of the HAProxy config
22
175
 
23
176
  ## Contributing
24
177
 
@@ -27,3 +180,31 @@ Write usage instructions here
27
180
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
181
  4. Push to the branch (`git push origin my-new-feature`)
29
182
  5. Create new Pull Request
183
+
184
+ ### Creating a Service Watcher ###
185
+
186
+ If you'd like to create a new service watcher:
187
+
188
+ 1. Create a file for your watcher in `service_watcher` dir
189
+ 2. Use the following template:
190
+ ```ruby
191
+ require_relative "./base"
192
+ module Synapse
193
+ class NewWatcher < BaseWatcher
194
+ def start
195
+ # write code which begins running service discovery
196
+ end
197
+
198
+ private
199
+ def validate_discovery_opts
200
+ # here, validate any required options in @discovery
201
+ end
202
+ end
203
+ end
204
+ ```
205
+
206
+ 3. Implement the `start` and `validate_discovery_opts` methods
207
+ 4. Implement whatever additional methods your discovery requires
208
+
209
+ When your watcher detects a list of new backends, they should be written to `@backends`.
210
+ You should then call `@synapse.configure` to force synapse to update the HAProxy config.
data/Vagrantfile ADDED
@@ -0,0 +1,112 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ this_dir = File.dirname(File.expand_path __FILE__)
5
+
6
+ Vagrant::Config.run do |config|
7
+ # All Vagrant configuration is done here. The most common configuration
8
+ # options are documented and commented below. For a complete reference,
9
+ # please see the online documentation at vagrantup.com.
10
+
11
+ # Every Vagrant virtual environment requires a box to build off of.
12
+ config.vm.box = "precise64"
13
+
14
+ # The url from where the 'config.vm.box' box will be fetched if it
15
+ # doesn't already exist on the user's system.
16
+ # config.vm.box_url = "http://domain.com/path/to/above.box"
17
+
18
+ config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
19
+
20
+ # Boot with a GUI so you can see the screen. (Default is headless)
21
+ # config.vm.boot_mode = :gui
22
+
23
+ # Assign this VM to a host-only network IP, allowing you to access it
24
+ # via the IP. Host-only networks can talk to the host machine as well as
25
+ # any other machines on the same network, but cannot be accessed (through this
26
+ # network interface) by any external networks.
27
+ # config.vm.network :hostonly, "192.168.33.10"
28
+
29
+ # Assign this VM to a bridged network, allowing you to connect directly to a
30
+ # network using the host's network device. This makes the VM appear as another
31
+ # physical device on your network.
32
+ # config.vm.network :bridged
33
+
34
+ # Forward a port from the guest to the host, which allows for outside
35
+ # computers to access the VM, whereas host only networking does not.
36
+ # config.vm.forward_port 80, 8080
37
+
38
+ # Share an additional folder to the guest VM. The first argument is
39
+ # an identifier, the second is the path on the guest to mount the
40
+ # folder, and the third is the path on the host to the actual folder.
41
+ # config.vm.share_folder "v-data", "/vagrant_data", "../data"
42
+
43
+ config.vm.share_folder 'this_dir', '/this_dir', this_dir
44
+
45
+ # Enable provisioning with Puppet stand alone. Puppet manifests
46
+ # are contained in a directory path relative to this Vagrantfile.
47
+ # You will need to create the manifests directory and a manifest in
48
+ # the file base.pp in the manifests_path directory.
49
+ #
50
+ # An example Puppet manifest to provision the message of the day:
51
+ #
52
+ # # group { "puppet":
53
+ # # ensure => "present",
54
+ # # }
55
+ # #
56
+ # # File { owner => 0, group => 0, mode => 0644 }
57
+ # #
58
+ # # file { '/etc/motd':
59
+ # # content => "Welcome to your Vagrant-built virtual machine!
60
+ # # Managed by Puppet.\n"
61
+ # # }
62
+ #
63
+ # config.vm.provision :puppet do |puppet|
64
+ # puppet.manifests_path = "manifests"
65
+ # puppet.manifest_file = "base.pp"
66
+ # end
67
+
68
+ # Enable provisioning with chef solo, specifying a cookbooks path, roles
69
+ # path, and data_bags path (all relative to this Vagrantfile), and adding
70
+ # some recipes and/or roles.
71
+ #
72
+ config.vm.provision :chef_solo do |chef|
73
+ # chef.cookbooks_path = "../my-recipes/cookbooks"
74
+ # chef.roles_path = "../my-recipes/roles"
75
+ # chef.data_bags_path = "../my-recipes/data_bags"
76
+ # chef.add_recipe "mysql"
77
+ # chef.add_role "web"
78
+
79
+ # You may also specify custom JSON attributes:
80
+ # chef.json = { :mysql_password => "foo" }
81
+
82
+ chef.cookbooks_path = File.join(this_dir, "chef/cookbooks")
83
+ chef.add_recipe "synapse"
84
+ end
85
+
86
+ # Enable provisioning with chef server, specifying the chef server URL,
87
+ # and the path to the validation key (relative to this Vagrantfile).
88
+ #
89
+ # The Opscode Platform uses HTTPS. Substitute your organization for
90
+ # ORGNAME in the URL and validation key.
91
+ #
92
+ # If you have your own Chef Server, use the appropriate URL, which may be
93
+ # HTTP instead of HTTPS depending on your configuration. Also change the
94
+ # validation key to validation.pem.
95
+ #
96
+ # config.vm.provision :chef_client do |chef|
97
+ # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
98
+ # chef.validation_key_path = "ORGNAME-validator.pem"
99
+ # end
100
+ #
101
+ # If you're using the Opscode platform, your validator client is
102
+ # ORGNAME-validator, replacing ORGNAME with your organization name.
103
+ #
104
+ # IF you have your own Chef Server, the default validation client name is
105
+ # chef-validator, unless you changed the configuration.
106
+ #
107
+ # chef.validation_client_name = "ORGNAME-validator"
108
+
109
+ Vagrant::Config.run do |config|
110
+ config.vm.provision :shell, :path => "test.sh"
111
+ end
112
+ end
data/bin/synapse ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'optparse'
5
+
6
+ require_relative "../lib/synapse"
7
+
8
+ options={}
9
+
10
+ # set command line options
11
+ optparse = OptionParser.new do |opts|
12
+ opts.banner =<<EOB
13
+ Welcome to synapse
14
+
15
+ Usage: synapse --config /path/to/synapse/config
16
+ EOB
17
+
18
+ options[:config] = ENV['SYNAPSE_CONFIG']
19
+ opts.on('-c config','--config config', String, 'path to synapse config') do |key,value|
20
+ options[:config] = key
21
+ end
22
+
23
+ opts.on( '-h', '--help', 'Display this screen' ) do
24
+ puts opts
25
+ exit
26
+ end
27
+
28
+ end
29
+
30
+
31
+ # parse command line arguments
32
+ optparse.parse!
33
+
34
+
35
+ # parse synapse config file
36
+ begin
37
+ config = JSON::parse(File.read(options[:config]))
38
+ rescue Errno::ENOENT => e
39
+ raise ArgumentError, "config file does not exist:\n#{e.inspect}"
40
+ rescue Errno::EACCES => e
41
+ raise ArgumentError, "could not open config file:\n#{e.inspect}"
42
+ rescue JSON::ParserError => e
43
+ raise "config file #{options[:config]} is not json:\n#{e.inspect}"
44
+ end
45
+
46
+
47
+ # create synapse object
48
+ s = Synapse::Synapse.new config
49
+
50
+ # start synapse
51
+ s.run
52
+
53
+
54
+ puts "exiting synapse"
data/chef/converge ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/bash -e
2
+
3
+ cd `dirname $0`
4
+ sudo /opt/vagrant_ruby/bin/chef-solo -c run.rb -j run.json
@@ -0,0 +1,2 @@
1
+ package 'lxc'
2
+
@@ -0,0 +1 @@
1
+ default.synapse.dir = '/opt/synapse'
@@ -0,0 +1,6 @@
1
+ package 'haproxy'
2
+ gem_package 'bundler'
3
+
4
+ directory node.synapse.dir do
5
+ recursive true
6
+ end
data/chef/run.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "run_list": [
3
+ "recipe[synapse]"
4
+ ],
5
+ "synapse": {
6
+ }
7
+ }
8
+
data/chef/run.rb ADDED
@@ -0,0 +1,2 @@
1
+ this_dir = File.dirname(File.expand_path __FILE__)
2
+ cookbook_path File.join(this_dir,'cookbooks')
data/client/.RData ADDED
Binary file