jn_services 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cee0b10f7814fc15d743fbec9be6c8b727fb7f34
4
+ data.tar.gz: 29f644614ad66c76051774e77f798873b3be4a3e
5
+ SHA512:
6
+ metadata.gz: 9ab1f4d25e5b846a14f929bfe289c32692446199db1a4a8cbaef12c17c7e5e2aab2575081d622991afaca6d57732d7b388c799ab8920169ccfa27b266c2622e2
7
+ data.tar.gz: 262b787d5ea448e9a187e0fcbf399f4ef173304439cdeb570b6bd489134b78b620058b1067269fa690c95ef6f6156a42d9e3bdf6beaf839d5ebfb6136bb9c73c
data/.coco.yml ADDED
@@ -0,0 +1,5 @@
1
+ :directories:
2
+ - lib
3
+ :excludes:
4
+ - spec
5
+ - .bundle
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ _yardoc
8
+ coverage
9
+ spec/reports
10
+ tmp
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ Includes:
3
+ - Gemfile
4
+ - lib/**
5
+ Excludes:
6
+ - test/**
7
+ - vendor/**
8
+
9
+ Encoding:
10
+ Enabled: false
11
+
12
+ LineLength:
13
+ Max: 100
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ before_install:
2
+ - bash build_etcd
3
+ - bundle install --path .bundle
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.0
8
+ branches:
9
+ only:
10
+ - master
11
+ script: "ETCD_BIN=./etcd/bin/etcd bundle exec rake"
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ ### 1.2.0
2
+ * switch to rubocop
3
+ * update etcd gem to 0.0.6
4
+ * compatable with etcd 0.2.0 & 0.3.0
5
+
6
+ ### 1.1.1
7
+ * bugfix remove of options attr from endoint
8
+ * cleanup tailor issues
9
+
10
+ ### 1.1.0
11
+ * add all method to services that returns an array of all services
12
+ * add subscribed method to services that returns array of all services a node is mapped too
13
+
14
+ ### 1.0.6
15
+ * revert naming change
16
+
17
+ ### 1.0.5
18
+ * Fix bug where .load method was returnign a hash instead of self
19
+ * make compatable with the 0.0.4 etcd-ruby gem
20
+ * make service return objects for endpoint and members instead of hashes.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in etcd.gemspec
4
+ gemspec
5
+ gem 'etcd', '~> 0.0.6'
6
+
7
+ group :dev do
8
+ gem 'simplecov'
9
+ gem 'rubocop'
10
+ gem 'uuid'
11
+ # aparently macaddr has broken gem dep
12
+ gem 'systemu'
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jesse Nelson
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.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/spheromak/services.png?branch=master)](https://travis-ci.org/spheromak/services)
2
+
3
+ Services Library
4
+
5
+ Sets up libraries for defining endpoints and serevicess ontop of etcd
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rdoc/task"
4
+
5
+ task default: 'test:quick'
6
+
7
+
8
+ RDoc::Task.new do |rdoc|
9
+ rdoc.main = "README.rdoc"
10
+ rdoc.rdoc_files.include("lib /*.rb")
11
+ end
12
+
13
+ namespace :test do
14
+ RSpec::Core::RakeTask.new("spec") do |t|
15
+ t.rspec_opts = '--color --fail-fast'
16
+ end
17
+
18
+ begin
19
+ require 'rubocop/rake_task'
20
+ Rubocop::RakeTask.new do |task|
21
+ task.fail_on_error = true
22
+ end
23
+ rescue LoadError
24
+ warn 'Rubocop gem not installed, now the code will look like crap!'
25
+ end
26
+
27
+ desc 'Run all of the quick tests.'
28
+ task :quick do
29
+ Rake::Task['test:rubocop'].invoke
30
+ Rake::Task['test:spec'].invoke
31
+ end
32
+ end
data/build_etcd ADDED
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ wget -c https://go.googlecode.com/files/go1.2.linux-amd64.tar.gz
4
+ tar -zxf go1.2.linux-amd64.tar.gz
5
+ git clone https://github.com/coreos/etcd
6
+ export GOROOT=$PWD/go
7
+ export PATH=$GOROOT/bin:$PATH
8
+ cd etcd && ./build
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'services/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "jn_services"
9
+ spec.version = Services::VERSION
10
+ spec.authors = ["Jesse Nelson"]
11
+ spec.email = ["spheromak@gmail.com"]
12
+ spec.description = %q{Consitently model servives with etcd in and outside chef}
13
+ spec.summary = spec.description
14
+ spec.homepage = "https://github.com/spheromak/services"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "etcd"
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,148 @@
1
+ module Services
2
+ #
3
+ # Setup ETCD connection via chef or plain host
4
+ # Also stores that aconnection in Services.connection
5
+ # Most other classes require this to be setup
6
+ #
7
+ class Connection
8
+ require 'openssl'
9
+
10
+ attr_reader :node, :run_context, :client, :host, :port, :ssl_verify
11
+
12
+ if defined?(Chef) == 'constant' && Chef.class == Class
13
+ if Chef::Version.new(Chef::VERSION) <= Chef::Version.new('11.0.0')
14
+ include ::Chef::Mixin::Language
15
+ else
16
+ include ::Chef::DSL::DataQuery
17
+ end
18
+ end
19
+
20
+ #
21
+ # Initialize etcd client
22
+ #
23
+ # You should pass either a run_context or explicit host/port arguments
24
+ # the run_context will take prescedence
25
+ #
26
+ # @param [Hash] options
27
+ # @option args [Chef::RunContext] :run_context (nil)
28
+ # The chef run context to find things in
29
+ # @option args [String] :host (nil) The host address to connect too
30
+ # @option args [String] :port (4001) The etcd port to connect too
31
+ def initialize(args)
32
+ @run_context = args.fetch(:run_context, nil)
33
+ Services.run_context = args[:run_context]
34
+ @node = args[:run_context].node if run_context
35
+ @host = args[:host]
36
+ @port = args[:port] || 4001
37
+ @ssl_verify = args[:verify] || OpenSSL::SSL::VERIFY_NONE
38
+
39
+ validate
40
+ load_gem
41
+ Services.connection = get_connection(find_servers)
42
+ end
43
+
44
+ private
45
+
46
+ #
47
+ # Validate args passeed in on initialize
48
+ #
49
+ # We require run_context OR host to function
50
+ #
51
+ def validate
52
+ unless run_context || host
53
+ fail ArgumentError, 'Must provide a run_context OR host to initialize'
54
+ end
55
+ end
56
+
57
+ #
58
+ # Lazily Load the gem requirement so we can run inside chef
59
+ #
60
+ # If @run_context exists it will use that to install the gem via
61
+ # Chefs chef_gem resource
62
+ #
63
+ def load_gem
64
+ require 'etcd'
65
+ rescue LoadError
66
+ if run_context
67
+ Chef::Log.info 'etcd gem not found. attempting to install'
68
+ g = Chef::Resource::ChefGem.new 'etcd', run_context
69
+ g.version '0.0.6'
70
+ run_context.resource_collection.insert g
71
+ g.run_action :install
72
+ require 'etcd'
73
+ end
74
+ end
75
+
76
+ #
77
+ # Find other Etd Servers by looking at node attributes or via Chef Search
78
+ #
79
+ def find_servers
80
+ # need a run_context to find anything in
81
+ return nil unless run_context
82
+ # If there are already servers in attribs use those
83
+ return node[:etcd][:servers] if node.key?(:etcd) &&
84
+ node[:etcd].key?(:servers)
85
+
86
+ # if we have already searched in this run use those
87
+ return node.run_state[:etcd_servers] if node.run_state.key? :etcd_servers
88
+
89
+ # find nodes and build array of ip's
90
+ etcd_nodes = search(:node, search_query)
91
+ servers = etcd_nodes.map { |n| n[:ipaddress] }
92
+
93
+ # store that in the run_state
94
+ node.run_state[:etcd_servers] = servers
95
+ end
96
+
97
+ #
98
+ # Setup proper chef search term for other etcd boxen
99
+ #
100
+ # Will search for known recipe in run_list or specified term
101
+ #
102
+ def search_query
103
+ query = "(chef_environment:#{node.chef_environment} "
104
+ query << 'AND recipes:etcd) '
105
+ if node[:etcd][:recipe]
106
+ query << "OR (chef_environment:#{node.chef_environment} "
107
+ query << "AND #{node[:etcd][:search_term]})"
108
+ end
109
+ query
110
+ end
111
+
112
+ #
113
+ # connect to ip/port and store in @@client
114
+ # If given an arry of servers then try each until we
115
+ # connect
116
+ # TODO: refactor
117
+ # rubocop:disable MethodLength
118
+ def get_connection(servers = nil)
119
+ c = nil
120
+ if servers
121
+ servers.each do |s|
122
+ c = try_connect(s)
123
+ break if c
124
+ end
125
+ else
126
+ c = try_connect host
127
+ end
128
+ fail 'Unable to get a valid connection to Etcd' unless c
129
+ c
130
+ end
131
+
132
+ #
133
+ # Try to grab an etcd connection
134
+ #
135
+ # @param [String] server () The server to try to connect too
136
+ #
137
+ def try_connect(server)
138
+ c = ::Etcd.client(host: server, port: port)
139
+ begin
140
+ c.get '/_etcd/machines'
141
+ return c
142
+ rescue
143
+ puts "ETCD: failed to connect to #{c.host}:#{c.port}"
144
+ return nil
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,24 @@
1
+ # Services::Endpoint
2
+ # The VIP of the service. This class describes where an endpoint lives
3
+ #
4
+ module Services
5
+ require_relative 'entity'
6
+
7
+ # endpoint describes a VIP ip
8
+ class Endpoint < Services::Entity
9
+ attr_accessor :ip, :port, :proto
10
+ def initialize(name, args = {})
11
+ @ip = args[:ip] || ''
12
+ @proto = args[:proto] || 'http'
13
+ @port = args[:port] || 80
14
+ @path = "#{name}/endpoint"
15
+ super
16
+ end
17
+
18
+ private
19
+
20
+ def validate
21
+ fail 'endpont requires a service name' unless name
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ #
2
+ # Services::Entity:
3
+ # This is the generic 'entity' for anything you want to attach to a service.
4
+ # Inherit this for any other type a service may have.
5
+ module Services
6
+ require_relative '../services'
7
+
8
+ # entity base class.
9
+ # member,service,endpoint are all "services::entity"
10
+ class Entity
11
+ attr_reader :name, :path
12
+ def initialize(name, args = {})
13
+ @name = name
14
+ validate
15
+ end
16
+
17
+ def store
18
+ _store
19
+ end
20
+ alias_method :save, :store
21
+
22
+ def load
23
+ _load
24
+ end
25
+
26
+ def to_hash
27
+ vars = {}
28
+ instance_variables.each do |name|
29
+ key = name[1..-1].to_s
30
+ # don't store "path"
31
+ next if key == 'path'
32
+ vars[key] = instance_variable_get(name).to_s
33
+ end
34
+ vars
35
+ end
36
+
37
+ private
38
+
39
+ def validate
40
+ fail 'This class should be extended. Not used directly' unless path
41
+ end
42
+
43
+ def _store
44
+ to_hash.each do |k, v|
45
+ next if k == 'name'
46
+ # etcd doesn't like nil
47
+ v ||= ''
48
+ Services.set "#{KEY}/#{path}/#{k}", v
49
+ end
50
+ end
51
+
52
+ def _load
53
+ return unless valid_path
54
+ to_hash.each do |k, v|
55
+ next if k == 'name'
56
+ value = Services.get("#{KEY}/#{path}/#{k}").value
57
+ instance_variable_set "@#{k}", value
58
+ end
59
+ self
60
+ end
61
+
62
+ def valid_path
63
+ begin
64
+ Services.get("#{KEY}/#{path}")
65
+ rescue
66
+ return false
67
+ end
68
+ true
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'entity'
2
+
3
+ module Services
4
+ # This is a service member usually something that
5
+ # would be sitting behind some VIP
6
+ class Member < Services::Entity
7
+ attr_accessor :ip, :port, :proto, :service, :weight
8
+ def initialize(name, args = {})
9
+ @ip = args[:ip] || ''
10
+ @proto = args[:proto] || 'http'
11
+ @port = args[:port] || 80
12
+ @weight = args[:weight] || 20
13
+ @service = args[:service]
14
+ @path = "#{service}/members/#{name}"
15
+ super
16
+ end
17
+
18
+ private
19
+
20
+ def validate
21
+ unless @service
22
+ fail ArgumentError,
23
+ "#{self.class} requires service: argument"
24
+ end
25
+
26
+ unless name
27
+ fail ARgumentError,
28
+ "#{self.class} requires name argument"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,59 @@
1
+ # Services::Service
2
+ # this is the almalgamate service class which allows you to load a service
3
+ # and it's endpoint/members
4
+ #
5
+ # TODO: Allow arbitrary entity loading.
6
+ #
7
+ module Services
8
+ require_relative 'connection'
9
+ require_relative 'endpoint'
10
+ require_relative 'member'
11
+
12
+ # service container
13
+ class Service
14
+ attr_reader :name
15
+ attr_reader :members
16
+ attr_reader :endpoint
17
+
18
+ def initialize(name)
19
+ @name = name
20
+ @members = []
21
+ @endpoint = Services::Endpoint.new name
22
+
23
+ create_if_missing
24
+ load_members
25
+ load_endpoint
26
+ end
27
+
28
+ private
29
+
30
+ def create_if_missing
31
+ Services.get "#{KEY}/#{name}"
32
+ rescue Net::HTTPServerException => e
33
+ Services.set "#{KEY}/#{name}/_created", Time.now if e.message.match 'Not Found'
34
+ end
35
+
36
+ def load_endpoint
37
+ endpoint.load
38
+ endpoint
39
+ end
40
+
41
+ # rubocop:disable MethodLength
42
+ def load_members
43
+ begin
44
+ etcd_members = Services.get "#{KEY}/#{name}/members"
45
+ rescue Net::HTTPServerException => e
46
+ etcd_members = nil if e.message.match 'Not Found'
47
+ end
48
+
49
+ unless etcd_members.nil? || etcd_members.empty?
50
+ etcd_members.node.nodes.each do |m|
51
+ m_name = File.basename m['key']
52
+ m1 = Services::Member.new(m_name, service: name)
53
+ m1.load
54
+ @members.push m1
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,4 @@
1
+ # setup the version here
2
+ module Services
3
+ VERSION = '1.0.0'
4
+ end
data/lib/services.rb ADDED
@@ -0,0 +1,53 @@
1
+ #
2
+ # Services Module
3
+ #
4
+ # Uses etcd to manage state of Service Endpoint & Members
5
+ #
6
+ module Services
7
+ require_relative 'services/version'
8
+ require_relative 'services/connection'
9
+ require_relative 'services/entity'
10
+ require_relative 'services/service'
11
+ require_relative 'services/endpoint'
12
+ require_relative 'services/member'
13
+
14
+ # this will change or be slurped up from a config/node attrib
15
+ KEY = '/services'
16
+
17
+ #
18
+ # Share a connection between all classess using this module
19
+ #
20
+ class << self
21
+ attr_accessor :connection, :run_context
22
+
23
+ def get(*args)
24
+ Chef::Log.debug "connection.get args #{args}" unless run_context.nil?
25
+ connection.get(*args)
26
+ end
27
+
28
+ def set(*args)
29
+ Chef::Log.debug "connection.set args #{args}" unless run_context.nil?
30
+ connection.set(*args)
31
+ end
32
+
33
+ # return a list of all services
34
+ def all
35
+ services = []
36
+ get(KEY).each do |s|
37
+ name = File.basename s.key
38
+ puts "loading #{name}"
39
+ services << Services::Service.new(name)
40
+ end
41
+ services
42
+ end
43
+
44
+ # return all services a node is subscribed to
45
+ def subscribed(f = nil)
46
+ fail 'param and run_context can not both be nil' if f.nil? && run_context.nil?
47
+
48
+ fqdn = f.nil? ? rteraun_context.node.fqdn : f
49
+ services = all.map { |s| s.members.include?(fqdn) ? s : nil }
50
+ services.compact
51
+ end
52
+ end
53
+ end
data/spec/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_from:
2
+ - ../.rubocop.yml
3
+
4
+ LineLength:
5
+ Max: 200
@@ -0,0 +1,47 @@
1
+ describe 'Services::Endpoint' do
2
+ before(:each) do
3
+ @ep_data = { 'name' => 'test', 'ip' => '127.0.0.1', 'proto' => 'http', 'port' => '80' }
4
+ Services::Connection.new host: 'localhost'
5
+ @ep = Services::Endpoint.new 'test', ip: '127.0.0.1', port: 80
6
+ end
7
+
8
+ it 'should raise without a service name' do
9
+ expect { Services::Endpoint.new }.to raise_error
10
+ end
11
+
12
+ describe '#to_hash' do
13
+ it 'should return each pair of insance vars' do
14
+ @ep.to_hash.should eql @ep_data
15
+ end
16
+ end
17
+
18
+ describe '#store' do
19
+ it 'should store' do
20
+ puts "storing #{@ep.inspect}"
21
+ @ep.store
22
+ end
23
+ end
24
+
25
+ describe '#load' do
26
+ before do
27
+ @ep = Services::Endpoint.new 'test'
28
+ end
29
+
30
+ it 'should handle no endpoint' do
31
+ e = Services::Endpoint.new 'test_endpoint_fail'
32
+ e.load
33
+ end
34
+
35
+ it 'should load' do
36
+ @ep.load
37
+ @ep.to_hash.should eql @ep_data
38
+ end
39
+
40
+ it 'should get ip' do
41
+ @ep.ip.should eql ''
42
+ @ep.load
43
+ @ep.ip.should eql '127.0.0.1'
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ describe 'Services::Member' do
2
+ before(:each) do
3
+ @m_data = { 'ip' => '127.0.0.2', 'proto' => 'http', 'port' => '80', 'weight' => '20', 'service' => 'test', 'name' => 'test_member' }
4
+ Services::Connection.new host: 'localhost'
5
+ @m = Services::Member.new 'test_member', service: 'test', ip: '127.0.0.2', port: 80, weight: 20
6
+ @m2 = Services::Member.new 'test_member2', service: 'test', ip: '127.0.0.3', port: 80, weight: 20
7
+ end
8
+
9
+ it 'should raise without a service name' do
10
+ expect { Services::Member.new 'foo' }.to raise_error
11
+ end
12
+
13
+ describe '#to_hash' do
14
+ it 'should return each pair of insance vars' do
15
+ @m.to_hash.should == @m_data
16
+ end
17
+ end
18
+
19
+ describe '#store' do
20
+ it 'should store' do
21
+ @m.store
22
+ @m2.store
23
+ end
24
+ end
25
+
26
+ describe '#load' do
27
+ before do
28
+ @m = Services::Member.new 'test_member', service: 'test'
29
+ end
30
+
31
+ it 'should load' do
32
+ @m.load
33
+ @m.to_hash.should == @m_data
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Services::Service' do
3
+ let(:c) { Services::Connection.new host: 'localhost' }
4
+ let(:m1) { Services::Member.new 'test_member', service: 'test', ip: '127.0.0.2', port: 80, weight: 20 }
5
+ let(:m2) { Services::Member.new 'test_member2', service: 'test', ip: '127.0.0.3', port: 80, weight: 20 }
6
+
7
+ it 'should load the test service' do
8
+ Services::Connection.new host: 'localhost'
9
+ members = Services::Service.new('test').members
10
+ members.length.should eql 2
11
+ members[0].to_hash.should eql m1.to_hash
12
+ members[1].to_hash.should eql m2.to_hash
13
+ end
14
+
15
+ it 'should handle non-existant services' do
16
+ expect { Services::Service.new 'should_not_exist' }.to_not raise_error
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ require_relative 'spec_helper.rb'
2
+
3
+ describe 'Services' do
4
+ describe '::Connection' do
5
+ it 'should setup' do
6
+ Services::Connection.new host: 'localhost'
7
+ end
8
+
9
+ it 'should fail without something to connect too' do
10
+ expect { Services::Connection.new }.to raise_error
11
+ end
12
+
13
+ # chef functions in chef-spec specs ?
14
+ # it "should accept a chef run_context" do
15
+ # Services::Connection.new(
16
+ # run_context: Chef::RunContext.new(
17
+ # Chef::Node.new,
18
+ # Chef::CookbookCollection.new,
19
+ # Chef::EventDispatch::Dispatcher.new
20
+ # )
21
+ # )
22
+ # end
23
+ end
24
+
25
+ before(:each) do
26
+ Services::Connection.new host: 'localhost'
27
+ end
28
+
29
+ it 'can set' do
30
+ Services.set '/test/1', 1
31
+ end
32
+
33
+ it 'can get' do
34
+ Services.get '/_etcd/machines'
35
+ end
36
+
37
+ describe '::Entity' do
38
+ before(:each) do
39
+ Services::Connection.new host: 'localhost'
40
+ end
41
+
42
+ it 'should raise when directly instanced' do
43
+ expect { Services::Entity.new('foo') }.to raise_error(RuntimeError)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,104 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+ require_relative '../lib/services.rb'
4
+
5
+ require 'uuid'
6
+ require 'etcd'
7
+ # thanks @ranjib for this handy helper
8
+ module Etcd
9
+ # helpers to start an etcd cluster
10
+ # rubocop:disable ClassVars
11
+ module SpecHelper
12
+ @@pids = []
13
+
14
+ def self.etcd_binary
15
+ if File.exists? './etcd/etcd'
16
+ './etcd/etcd'
17
+ elsif !!ENV['ETCD_BIN']
18
+ ENV['ETCD_BIN']
19
+ elsif File.exists? '/usr/local/bin/etcd'
20
+ '/usr/local/bin/etcd'
21
+ else
22
+ fail 'etcd binary not found., you need to set ETCD_BIN'
23
+ end
24
+ end
25
+
26
+ def self.start_etcd_servers
27
+ @@tmpdir = Dir.mktmpdir
28
+ pid = spawn_etcd_server(@@tmpdir + '/leader')
29
+ @@pids = Array(pid)
30
+ leader = '127.0.0.1:7001'
31
+ 4.times do |n|
32
+ client_port = 4002 + n
33
+ server_port = 7002 + n
34
+ pid = spawn_etcd_server(@@tmpdir + client_port.to_s, client_port, server_port, leader)
35
+ @@pids << pid
36
+ end
37
+ end
38
+
39
+ def self.stop_etcd_servers
40
+ @@pids.each do |pid|
41
+ Process.kill('TERM', pid)
42
+ end
43
+ FileUtils.remove_entry_secure(@@tmpdir, true)
44
+ end
45
+
46
+ def self.spawn_etcd_server(dir, client_port = 4001, server_port = 7001, leader = nil)
47
+ args = " -addr 127.0.0.1:#{client_port} -peer-addr 127.0.0.1:#{server_port} -data-dir #{dir} -name node_#{client_port}"
48
+ command = if leader.nil?
49
+ etcd_binary + args
50
+ else
51
+ etcd_binary + args + " -peers #{leader}"
52
+ end
53
+ pid = spawn(command, out: '/dev/null')
54
+ Process.detach(pid)
55
+ sleep 1
56
+ pid
57
+ end
58
+
59
+ def uuid
60
+ @uuid ||= UUID.new
61
+ end
62
+
63
+ def random_key(n = 1)
64
+ key = ''
65
+ n.times do
66
+ key << '/' + uuid.generate
67
+ end
68
+ key
69
+ end
70
+
71
+ def etcd_servers
72
+ (1..5).map { |n| "http://127.0.0.1:700#{n}" }
73
+ end
74
+
75
+ def other_client
76
+ Etcd.client
77
+ end
78
+
79
+ def read_only_client
80
+ Etcd.client(allow_redirect: false, port: 4004)
81
+ end
82
+ end
83
+ end
84
+
85
+ RSpec.configure do |config|
86
+
87
+ config.include Etcd::SpecHelper
88
+
89
+ config.before(:suite) do
90
+ Etcd::SpecHelper.start_etcd_servers
91
+ end
92
+
93
+ config.after(:suite) do
94
+ Etcd::SpecHelper.stop_etcd_servers
95
+ end
96
+ # Use color in STDOUT
97
+ config.color_enabled = true
98
+
99
+ # Use color not only in STDOUT but also in pagers and files
100
+ config.tty = true
101
+
102
+ # Use the specified formatter
103
+ config.formatter = :documentation # :progress, :html, :textmate
104
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jn_services
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jesse Nelson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: etcd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
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: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Consitently model servives with etcd in and outside chef
70
+ email:
71
+ - spheromak@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .coco.yml
77
+ - .gitignore
78
+ - .rubocop.yml
79
+ - .travis.yml
80
+ - CHANGELOG.md
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - build_etcd
86
+ - jn_services.gemspec
87
+ - lib/services.rb
88
+ - lib/services/connection.rb
89
+ - lib/services/endpoint.rb
90
+ - lib/services/entity.rb
91
+ - lib/services/member.rb
92
+ - lib/services/service.rb
93
+ - lib/services/version.rb
94
+ - spec/.rubocop.yml
95
+ - spec/endpoint_spec.rb
96
+ - spec/member_spec.rb
97
+ - spec/service_spec.rb
98
+ - spec/services_spec.rb
99
+ - spec/spec_helper.rb
100
+ homepage: https://github.com/spheromak/services
101
+ licenses:
102
+ - MIT
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.0.14
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Consitently model servives with etcd in and outside chef
124
+ test_files:
125
+ - spec/.rubocop.yml
126
+ - spec/endpoint_spec.rb
127
+ - spec/member_spec.rb
128
+ - spec/service_spec.rb
129
+ - spec/services_spec.rb
130
+ - spec/spec_helper.rb