cluster_management 0.7 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/cluster_management/cluster.rb +15 -26
- data/lib/cluster_management/config.rb +37 -6
- data/lib/cluster_management/gems.rb +2 -1
- data/lib/cluster_management/integration/vos.rb +1 -52
- data/lib/cluster_management/service.rb +9 -36
- data/lib/cluster_management/support/exception.rb +2 -2
- data/lib/cluster_management.rb +7 -11
- data/readme.md +162 -125
- data/spec/cluster_spec.rb +25 -0
- data/spec/config_spec/app/config/config.yml +7 -0
- data/spec/config_spec.rb +23 -0
- data/spec/dependency_spec.rb +67 -0
- data/spec/service_spec.rb +80 -0
- data/spec/spec_helper.rb +4 -0
- metadata +27 -10
data/Rakefile
CHANGED
@@ -1,14 +1,7 @@
|
|
1
|
-
class ClusterManagement::Cluster
|
2
|
-
attr_accessor :config, :logger
|
3
|
-
attr_reader :boxes
|
4
|
-
|
5
|
-
def services &b
|
6
|
-
b ? @services.instance_eval(&b) : @services
|
7
|
-
end
|
8
|
-
|
1
|
+
class ClusterManagement::Cluster
|
9
2
|
class Services < BasicObject
|
10
3
|
def initialize
|
11
|
-
@h = ::Hash.new do |h, service_name|
|
4
|
+
@h = ::Hash.new do |h, service_name|
|
12
5
|
h[service_name] = ::ClusterManagement::Service.service_class(service_name).new
|
13
6
|
end
|
14
7
|
end
|
@@ -23,32 +16,28 @@ class ClusterManagement::Cluster
|
|
23
16
|
::Object.send :p, msg
|
24
17
|
end
|
25
18
|
|
26
|
-
def method_missing m
|
27
|
-
super unless a.blank? and b.blank?
|
19
|
+
def method_missing m
|
28
20
|
@h[m]
|
29
21
|
end
|
30
22
|
end
|
31
23
|
|
32
24
|
def initialize
|
33
25
|
@services = Services.new
|
34
|
-
|
26
|
+
|
35
27
|
@boxes = Hash.new do |h, host|
|
36
|
-
box = config.ssh
|
28
|
+
box = config.ssh ? Box.new(host.to_s, config.ssh) : Box.new(host.to_s)
|
37
29
|
box.open
|
38
30
|
h[host] = box
|
39
31
|
end
|
40
32
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
config.set! :tags, r
|
53
|
-
end
|
33
|
+
|
34
|
+
attr_reader :boxes
|
35
|
+
|
36
|
+
def services &b
|
37
|
+
b ? @services.instance_eval(&b) : @services
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_writer :config, :logger
|
41
|
+
def config; @config || raise("config not defined!") end
|
42
|
+
def logger; @logger || raise("logger not defined!") end
|
54
43
|
end
|
@@ -1,10 +1,41 @@
|
|
1
|
-
class ClusterManagement::Config <
|
2
|
-
def
|
3
|
-
|
4
|
-
|
1
|
+
class ClusterManagement::Config < Hash
|
2
|
+
def load_config! runtime_path
|
3
|
+
merge! runtime_path: runtime_path, config_path: "#{runtime_path}/config"
|
4
|
+
|
5
|
+
path = "#{runtime_path}/config/config.yml"
|
6
|
+
raise("config file must have .yml extension (#{path})!") unless path.end_with? '.yml'
|
7
|
+
|
8
|
+
data = ::YAML.load_file path
|
5
9
|
if data
|
6
|
-
data.must_be.a ::Hash
|
7
|
-
self.
|
10
|
+
data.must_be.a ::Hash
|
11
|
+
data.each{|k, v| self[k.to_sym] = v}
|
12
|
+
end
|
13
|
+
|
14
|
+
# converting :handy_scheme to :scheme
|
15
|
+
if include? :handy_scheme
|
16
|
+
raise "You can't use both :handy_scheme and :scheme!" if include?(:scheme)
|
17
|
+
self.scheme = {}
|
18
|
+
handy_scheme.each do |box, tags|
|
19
|
+
tags.each do |tag|
|
20
|
+
(self.scheme[tag.to_sym] ||= []) << box
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.require_attr *attrs
|
27
|
+
attrs.each do |m|
|
28
|
+
define_method(m){self[m] || raise("key :#{m} not defined!")}
|
8
29
|
end
|
9
30
|
end
|
31
|
+
require_attr :scheme, :config_path, :runtime_path
|
32
|
+
|
33
|
+
protected
|
34
|
+
def method_missing m, *args
|
35
|
+
if m =~ /=$/
|
36
|
+
self[m[0..-2].to_sym] = args.first
|
37
|
+
else
|
38
|
+
self[m]
|
39
|
+
end
|
40
|
+
end
|
10
41
|
end
|
@@ -1,18 +1,6 @@
|
|
1
1
|
class Vos::Box
|
2
2
|
include Vos::Helpers::Ubuntu
|
3
3
|
|
4
|
-
# alias_method :mark_without_service!, :mark!
|
5
|
-
# def mark! key
|
6
|
-
# key = key.respond_to(:marker) || key
|
7
|
-
# mark_without_service! key
|
8
|
-
# end
|
9
|
-
#
|
10
|
-
# alias_method :has_mark_without_service?, :has_mark?
|
11
|
-
# def has_mark? key
|
12
|
-
# key = key.respond_to(:marker) || key
|
13
|
-
# has_mark_without_service? key
|
14
|
-
# end
|
15
|
-
|
16
4
|
def apply_once key, &block
|
17
5
|
unless has_mark? key
|
18
6
|
block.call self
|
@@ -20,43 +8,4 @@ class Vos::Box
|
|
20
8
|
end
|
21
9
|
end
|
22
10
|
|
23
|
-
end
|
24
|
-
|
25
|
-
# module Vos
|
26
|
-
# class ServicesHelper < BasicObject
|
27
|
-
# def initialize box, class_namespace
|
28
|
-
# @box, @class_namespace = box, class_namespace
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# class ServiceCallback < BasicObject
|
32
|
-
# def initialize box, class_namespace, class_name
|
33
|
-
# @box, @class_namespace, @class_name = box, class_namespace, class_name
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# protected
|
37
|
-
# def method_missing m, *a, &b
|
38
|
-
# klass = "::#{@class_namespace.to_s.camelize}::#{@class_name.to_s.camelize}".constantize
|
39
|
-
# klass.new(@box).send m, *a, &b
|
40
|
-
# end
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# protected
|
44
|
-
# def method_missing class_name
|
45
|
-
# ServiceCallback.new(@box, @class_namespace, class_name)
|
46
|
-
# end
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# # class Box
|
50
|
-
# # def self.define_service_namespace class_namespace
|
51
|
-
# # define_method class_namespace do
|
52
|
-
# # ServicesHelper.new self, class_namespace
|
53
|
-
# # end
|
54
|
-
# # end
|
55
|
-
# # define_service_namespace :services
|
56
|
-
# # define_service_namespace :projects
|
57
|
-
# #
|
58
|
-
# # def already_required_services
|
59
|
-
# # @already_required_services ||= Set.new
|
60
|
-
# # end
|
61
|
-
# # end
|
62
|
-
# end
|
11
|
+
end
|
@@ -8,8 +8,9 @@ class ClusterManagement::Service
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
def tag tag = nil
|
11
|
+
def tag tag = nil
|
12
12
|
if tag
|
13
|
+
tag.must_be.a Symbol
|
13
14
|
@tag = tag
|
14
15
|
else
|
15
16
|
@tag || raise("service :#{service_name} not tagged!")
|
@@ -26,7 +27,7 @@ class ClusterManagement::Service
|
|
26
27
|
end
|
27
28
|
cache_method_with_params :service_class
|
28
29
|
|
29
|
-
def service_name; name.split('::').last.underscore end
|
30
|
+
def service_name; name.split('::').last.underscore.to_sym end
|
30
31
|
cache_method_with_params :service_name
|
31
32
|
end
|
32
33
|
|
@@ -38,46 +39,18 @@ class ClusterManagement::Service
|
|
38
39
|
def service_name; self.class.service_name end
|
39
40
|
|
40
41
|
def apply_once key, &block
|
41
|
-
boxes{|box| box.apply_once self.class.marker(key), &block}
|
42
|
+
boxes.each{|box| box.apply_once self.class.marker(key), &block}
|
42
43
|
end
|
43
44
|
|
44
|
-
def boxes
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
unless @boxes
|
49
|
-
hosts = config.tags!["#{self.class.tag}", nil] || []
|
50
|
-
@boxes = hosts.collect{|host| cluster.boxes[host]}
|
51
|
-
end
|
52
|
-
@boxes
|
45
|
+
def boxes
|
46
|
+
@boxes ||= begin
|
47
|
+
hosts = config.scheme[self.class.tag] || []
|
48
|
+
@boxes = hosts.collect{|host| cluster.boxes[host]}
|
53
49
|
end
|
54
50
|
end
|
55
51
|
|
56
|
-
def
|
52
|
+
def box
|
57
53
|
boxes.size.must_be == 1
|
58
54
|
boxes.first
|
59
55
|
end
|
60
|
-
|
61
|
-
# def require options
|
62
|
-
# # if args.size == 1 and args.first.is_a?(Hash)
|
63
|
-
# # services = args.first
|
64
|
-
# # elsif args.size == 2 and args.first.is_a?(Array)
|
65
|
-
# # services = {}
|
66
|
-
# # args.first.each{|klass| services[klass] = args.last}
|
67
|
-
# # else
|
68
|
-
# # raise 'invalid arguments'
|
69
|
-
# # end
|
70
|
-
#
|
71
|
-
# options.each do |service_name, method|
|
72
|
-
# key = "#{service_name}.#{method}"
|
73
|
-
# unless box.already_required_services.include? key
|
74
|
-
# klass.new(box).send method if klass.method_defined? method
|
75
|
-
# box.already_required_services << key
|
76
|
-
# end
|
77
|
-
# end
|
78
|
-
# end
|
79
|
-
#
|
80
|
-
# def already_required_services
|
81
|
-
#
|
82
|
-
# end
|
83
56
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
warn 'stack trace filtering enabled'
|
1
|
+
# warn 'stack trace filtering enabled'
|
2
2
|
|
3
3
|
Exception.metaclass_eval do
|
4
4
|
attr_accessor :filters
|
@@ -26,7 +26,7 @@ Exception.filters = [
|
|
26
26
|
"/class_loader",
|
27
27
|
"/micon"
|
28
28
|
]
|
29
|
-
|
29
|
+
Exception.filters = []
|
30
30
|
|
31
31
|
Exception.class_eval do
|
32
32
|
alias_method :set_backtrace_without_filter, :set_backtrace
|
data/lib/cluster_management.rb
CHANGED
@@ -3,15 +3,11 @@ require 'vos'
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'tilt'
|
5
5
|
require 'class_loader'
|
6
|
-
# require 'micon'
|
7
|
-
# require 'micon/rad'
|
8
6
|
|
9
7
|
module ClusterManagement
|
10
8
|
end
|
11
9
|
|
12
|
-
#
|
13
|
-
# Classes
|
14
|
-
#
|
10
|
+
# Libraries
|
15
11
|
%w(
|
16
12
|
support/exception
|
17
13
|
config
|
@@ -23,17 +19,17 @@ end
|
|
23
19
|
).each{|f| require "cluster_management/#{f}"}
|
24
20
|
|
25
21
|
|
26
|
-
#
|
27
22
|
# Cluster
|
28
|
-
#
|
29
23
|
CLUSTER = ClusterManagement::Cluster.new
|
30
|
-
def cluster;
|
24
|
+
def cluster; CLUSTER end
|
31
25
|
|
32
26
|
|
33
|
-
#
|
34
27
|
# Config & Logger
|
35
|
-
#
|
36
28
|
cluster.logger = ClusterManagement::CustomLogger.new STDOUT
|
37
29
|
cluster.logger.formatter = -> severity, datetime, progname, msg {msg}
|
38
30
|
|
39
|
-
cluster.config = ClusterManagement::Config.new
|
31
|
+
cluster.config = ClusterManagement::Config.new
|
32
|
+
|
33
|
+
# Handy shortcut
|
34
|
+
Service = ClusterManagement::Service
|
35
|
+
|
data/readme.md
CHANGED
@@ -1,141 +1,178 @@
|
|
1
|
-
# Simple
|
1
|
+
# Simple Cluster Management Tool
|
2
2
|
|
3
3
|
It may be **usefull if Your claster has about 1-10 boxes**, and tools like Chef, Puppet, Capistrano are too complex and proprietary for your needs.
|
4
|
-
**It's
|
4
|
+
**It's very easy**, there are only a 3 concept - Box, Cluster and Service.
|
5
5
|
|
6
|
-
|
7
|
-
addon it should be easy to add and get started with it.
|
6
|
+
Usage:
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
- package installation, dependencies, versioning
|
9
|
+
- process management, start/stop services
|
10
|
+
- deplyment
|
11
11
|
|
12
|
-
|
12
|
+
It's designed to be used with [Virtual File System][vfs], [Virtual Operation System][vos] and Rake, but it's not required, You can use other tools also.
|
13
13
|
|
14
|
-
|
14
|
+
## Core Concepts
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
installation_dir = '/usr/local/ruby'
|
20
|
-
ruby_name = "ruby-1.9.2-p136"
|
16
|
+
- Box - a PC with remote access.
|
17
|
+
- Service - some abstract thing (a package, running process, app, ...) that operates on 1..n boxes. It can perform install/update/start/stop/.. operations and request other services to perform things it needs.
|
18
|
+
- Deployment Scheme - defines how services are distributed among boxes.
|
21
19
|
|
22
|
-
|
23
|
-
tmp.bash "wget ftp://ftp.ruby-lang.org//pub/ruby/1.9/#{ruby_name}.tar.gz"
|
24
|
-
tmp.bash "tar -xvzf #{ruby_name}.tar.gz"
|
20
|
+
## Deployment Scheme
|
25
21
|
|
26
|
-
|
27
|
-
src_dir.bash "./configure --prefix=#{installation_dir}"
|
28
|
-
src_dir.bash 'make && make install'
|
29
|
-
end
|
22
|
+
Let's suppose that we want to deploy our App on a cluster of 3 boxes using the following scheme. **Tags** are used to define connections (N to M, althouth in example below it's 1 to 1) between **Boxes** and **Services**.
|
30
23
|
|
31
|
-
|
24
|
+
![Deployment Scheme][deployment_scheme]
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
26
|
+
Deployment scheme defined via box tags (in config) and service tags, see below.
|
27
|
+
|
28
|
+
## Boxes
|
29
|
+
|
30
|
+
Boxes are defined in config:
|
31
|
+
|
32
|
+
```yaml
|
33
|
+
handy_scheme:
|
34
|
+
'web1.app.com': ['web']
|
35
|
+
'web2.app.com': ['web']
|
36
|
+
'db.app.com': ['db']
|
37
|
+
```
|
38
|
+
|
39
|
+
## Services
|
40
|
+
|
41
|
+
Dependencies are defined by calling another services, it's easy to use and understand and allows high flexibility in configuration.
|
42
|
+
|
43
|
+
And, **it's 'smart'**, in sample below the App::deploy method is smart ennought to figure out that it needs the Ruby and MySQL Services and it will call for them to apply before itself.
|
44
|
+
|
45
|
+
You can specify that the package should be applied only once (see :apply_once), and use versioning (see :version) - change the version and it will be reapplied.
|
46
|
+
|
47
|
+
It supports iterative development and can figure out what services are missing and needs to be applied, You don't have to write all the config at once, do it by small steps, adding one package after another.
|
48
|
+
|
49
|
+
Below are our Services:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class Services::Ruby < Service
|
53
|
+
tag :web
|
54
|
+
version 4
|
55
|
+
|
56
|
+
def install
|
57
|
+
# it will be called only once, it will be called next time only if You change the version
|
58
|
+
apply_once :install do |box|
|
59
|
+
logger.info "installing :#{service_name}"
|
60
|
+
box.fake_bash "apt-get install ruby"
|
58
61
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Services::Thin < Service
|
66
|
+
tag :web
|
67
|
+
|
68
|
+
def restart
|
69
|
+
logger.info "restarting :#{service_name}"
|
70
|
+
boxes.each{|box| box.fake_bash 'thin restart'}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Services::MySql < Service
|
75
|
+
tag :db
|
76
|
+
def started
|
77
|
+
logger.info "ensuring mysql is running"
|
78
|
+
box.fake_bash 'mysql start' unless box.fake_bash('ps -A') =~ /mysql/
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Services::App < Service
|
83
|
+
tag :web
|
84
|
+
|
85
|
+
def install
|
86
|
+
services.ruby.install
|
87
|
+
apply_once :install do |box|
|
88
|
+
logger.info "installing :#{service_name}"
|
89
|
+
box.fake_bash "cd #{config.app_path} && git clone app"
|
83
90
|
end
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
91
|
+
end
|
92
|
+
|
93
|
+
def update
|
94
|
+
install
|
95
|
+
logger.info "updating :#{service_name}"
|
96
|
+
boxes.each{|box| box.fake_bash "cd #{config.app_path} && git pull app"}
|
97
|
+
end
|
98
|
+
|
99
|
+
def deploy
|
100
|
+
update
|
101
|
+
logger.info "deploying :#{service_name}"
|
102
|
+
services.my_sql.started
|
103
|
+
services.thin.restart
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Rake
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
desc 'deploy to cluster'
|
112
|
+
task :deploy do
|
113
|
+
cluster.services.app.deploy
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
Note: You don't have install services before deployment, the **App::deploy will resolve all dependencies automatically and fully configure all boxes from clean state** - it will install packages, ensure all needed services are running and only then will start deployment.
|
118
|
+
|
119
|
+
Now, type:
|
120
|
+
|
121
|
+
```bash
|
122
|
+
$ rake deploy
|
123
|
+
```
|
124
|
+
|
125
|
+
You'll se something like this:
|
126
|
+
|
127
|
+
```
|
128
|
+
installing :ruby
|
129
|
+
=> bash: 'apt-get install ruby'
|
130
|
+
installing :app
|
131
|
+
=> bash: 'cd /tmp/cm_example_app && git clone app'
|
132
|
+
updating :app
|
133
|
+
=> bash: 'cd /tmp/cm_example_app && git pull app'
|
134
|
+
deploying :app
|
135
|
+
ensuring mysql is running
|
136
|
+
=> bash: 'ps -A'
|
137
|
+
=> bash: 'mysql start'
|
138
|
+
restarting :thin
|
139
|
+
=> bash: 'thin restart'
|
140
|
+
```
|
141
|
+
|
142
|
+
Deploy one more time, notice now there's no installation of Ruby and App:
|
143
|
+
|
144
|
+
```
|
145
|
+
updating :app
|
146
|
+
=> bash: 'cd /tmp/cm_example_app && git pull app'
|
147
|
+
deploying :app
|
148
|
+
ensuring mysql is running
|
149
|
+
=> bash: 'ps -A'
|
150
|
+
=> bash: 'mysql start'
|
151
|
+
restarting :thin
|
152
|
+
=> bash: 'thin restart'
|
153
|
+
```
|
154
|
+
|
155
|
+
## Installation
|
156
|
+
|
157
|
+
```bash
|
158
|
+
$ gem install cluster_management
|
159
|
+
```
|
160
|
+
|
161
|
+
## Examples
|
162
|
+
|
163
|
+
Go to [example][:example] folder, there are full example, type 'rake deploy' and look at the output.
|
164
|
+
|
165
|
+
For simplicity it uses the 'localhost' instead of 3 remote boxes and 'fake_bash' that just prints command to console (because we don't want to actually alter our localhost).
|
166
|
+
But You can easily define actual remote PCs in config and replace 'fake_bash' with 'bash' and see it in the real action.
|
167
|
+
|
168
|
+
You can also see 'real' configuration I use to manage the [http://ruby-lang.info](http://ruby-lang.info) site, [my_cluster][my_cluster].
|
169
|
+
|
170
|
+
## Bugs, Suggestion, Discussions
|
171
|
+
|
172
|
+
Please feel free to submit bugs and proposals to the issue tab above, or contact me directly.
|
138
173
|
|
139
174
|
[my_cluster]: http://github.com/alexeypetrushin/my_cluster/tree/master/lib/packages
|
140
175
|
[vos]: http://github.com/alexeypetrushin/vos
|
141
|
-
[vfs]: http://github.com/alexeypetrushin/vfs
|
176
|
+
[vfs]: http://github.com/alexeypetrushin/vfs
|
177
|
+
[deployment_scheme]: https://github.com/alexeypetrushin/cluster_management/raw/master/readme/deployment_scheme.png
|
178
|
+
[example]: https://github.com/alexeypetrushin/cluster_management/tree/master/example
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Cluster" do
|
4
|
+
before do
|
5
|
+
@cluster = Cluster.new
|
6
|
+
@cluster.config = Config.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return service by it's name" do
|
10
|
+
service_class = mock
|
11
|
+
service_class.should_receive(:new).and_return('mongo_db')
|
12
|
+
Service.stub!(:service_class).and_return(service_class)
|
13
|
+
|
14
|
+
@cluster.services[:mongo_db].should == 'mongo_db'
|
15
|
+
@cluster.services.mongo_db.should == 'mongo_db'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return box by it's name" do
|
19
|
+
box = mock
|
20
|
+
box.should_receive(:open)
|
21
|
+
Box.stub!(:new).and_return(box)
|
22
|
+
|
23
|
+
@cluster.boxes['app.com'].should == box
|
24
|
+
end
|
25
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Config" do
|
4
|
+
it "should load and parse config file" do
|
5
|
+
config = Config.new
|
6
|
+
config.load_config! "#{spec_dir}/app"
|
7
|
+
|
8
|
+
{
|
9
|
+
config_path: "#{spec_dir}/app/config",
|
10
|
+
runtime_path: "#{spec_dir}/app",
|
11
|
+
|
12
|
+
scheme: {
|
13
|
+
app: ['web1.app.com', 'web2.app.com'],
|
14
|
+
ruby: ['web1.app.com', 'web2.app.com'],
|
15
|
+
db: ['db.app.com']
|
16
|
+
},
|
17
|
+
|
18
|
+
nginx: {'port' => 80}
|
19
|
+
}.each do |k, v|
|
20
|
+
config[k].should == v
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Dependency' do
|
4
|
+
before :all do
|
5
|
+
module Services; end
|
6
|
+
|
7
|
+
class ServiceStub < Service
|
8
|
+
def apply_once key, &b
|
9
|
+
boxes.each &b
|
10
|
+
end
|
11
|
+
|
12
|
+
def boxes
|
13
|
+
[ServiceStub.box]
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_accessor :box
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class BoxStub < Array
|
22
|
+
alias_method :bash, :push
|
23
|
+
end
|
24
|
+
end
|
25
|
+
after(:all){remove_constants :Services, :ServiceStub, :BoxStub}
|
26
|
+
|
27
|
+
it "dependency resolving" do
|
28
|
+
class Services::Ruby < ServiceStub
|
29
|
+
def install
|
30
|
+
apply_once(:install){|box| box.bash 'install ruby'}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Services::Server < ServiceStub
|
35
|
+
def restart
|
36
|
+
boxes.each{|box| box.bash 'restart server'}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Services::App < ServiceStub
|
41
|
+
def install
|
42
|
+
services.ruby.install
|
43
|
+
apply_once(:install){|box| box.bash 'install app'}
|
44
|
+
end
|
45
|
+
|
46
|
+
def update
|
47
|
+
install
|
48
|
+
boxes.each{|box| box.bash 'update app'}
|
49
|
+
end
|
50
|
+
|
51
|
+
def deploy
|
52
|
+
update
|
53
|
+
services.server.restart
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ServiceStub.box = BoxStub.new
|
58
|
+
|
59
|
+
Services::App.new.deploy
|
60
|
+
ServiceStub.box.should == [
|
61
|
+
'install ruby',
|
62
|
+
'install app',
|
63
|
+
'update app',
|
64
|
+
'restart server'
|
65
|
+
]
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Service" do
|
4
|
+
before{module Services; end}
|
5
|
+
after{remove_constants :Services}
|
6
|
+
|
7
|
+
it "should return service class by service name" do
|
8
|
+
class Services::App; end
|
9
|
+
Service.service_class(:app).should == Services::App
|
10
|
+
end
|
11
|
+
|
12
|
+
it "version, tag, marker" do
|
13
|
+
class Services::Db < Service
|
14
|
+
service_name.should == :db
|
15
|
+
end
|
16
|
+
|
17
|
+
-> {Services::Db.tag}.should raise_error(/not tagged/)
|
18
|
+
Services::Db.version.should == 1
|
19
|
+
|
20
|
+
class Services::Db
|
21
|
+
tag :db
|
22
|
+
version 2
|
23
|
+
|
24
|
+
tag.should == :db
|
25
|
+
version.should == 2
|
26
|
+
marker.should == 'db:2'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "shold return only boxes this service should be deployed" do
|
31
|
+
cluster = Cluster.new
|
32
|
+
cluster.stub!(:boxes).and_return(
|
33
|
+
'web1.app.com' => :web1, 'web2.app.com' => :web2, 'db.app.com' => :db
|
34
|
+
)
|
35
|
+
cluster.config = Config.new
|
36
|
+
cluster.config.scheme = {
|
37
|
+
app: ['web1.app.com', 'web2.app.com'],
|
38
|
+
db: ['db.app.com']
|
39
|
+
}
|
40
|
+
|
41
|
+
class Services::App < Service
|
42
|
+
tag :app
|
43
|
+
end
|
44
|
+
app = Services::App.new
|
45
|
+
app.stub!(:cluster).and_return(cluster)
|
46
|
+
|
47
|
+
app.send(:boxes).sort.should == [:web1, :web2]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "single box" do
|
51
|
+
class Services::App < Service; end
|
52
|
+
app = Services::App.new
|
53
|
+
app.stub!(:boxes).and_return([:a, :b])
|
54
|
+
-> {app.send(:box)}.should raise_error(AssertionError)
|
55
|
+
app.stub!(:boxes).and_return([:a])
|
56
|
+
app.send(:box).should == :a
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be applied only once if required" do
|
60
|
+
box = mock
|
61
|
+
box.should_receive :apply_once do |key, &b|
|
62
|
+
key.should == 'app:2:install'
|
63
|
+
b.call box
|
64
|
+
end
|
65
|
+
box.should_receive(:install_app)
|
66
|
+
|
67
|
+
class Services::App < Service
|
68
|
+
version 2
|
69
|
+
|
70
|
+
def install
|
71
|
+
apply_once :install do |box|
|
72
|
+
box.install_app
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
app = Services::App.new
|
77
|
+
app.stub!(:boxes).and_return([box])
|
78
|
+
app.install
|
79
|
+
end
|
80
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cluster_management
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.7.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-07-02 00:00:00.000000000 +04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: tilt
|
17
|
-
requirement: &
|
17
|
+
requirement: &3013040 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *3013040
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: ruby_ext
|
28
|
-
requirement: &
|
28
|
+
requirement: &3012690 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *3012690
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: vos
|
39
|
-
requirement: &
|
39
|
+
requirement: &3012450 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,21 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *3012450
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: vfs
|
50
|
+
requirement: &3012180 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *3012180
|
48
59
|
- !ruby/object:Gem::Dependency
|
49
60
|
name: class_loader
|
50
|
-
requirement: &
|
61
|
+
requirement: &3011950 !ruby/object:Gem::Requirement
|
51
62
|
none: false
|
52
63
|
requirements:
|
53
64
|
- - ! '>='
|
@@ -55,7 +66,7 @@ dependencies:
|
|
55
66
|
version: '0'
|
56
67
|
type: :runtime
|
57
68
|
prerelease: false
|
58
|
-
version_requirements: *
|
69
|
+
version_requirements: *3011950
|
59
70
|
description:
|
60
71
|
email:
|
61
72
|
executables: []
|
@@ -73,6 +84,12 @@ files:
|
|
73
84
|
- lib/cluster_management/service.rb
|
74
85
|
- lib/cluster_management/support/exception.rb
|
75
86
|
- lib/cluster_management.rb
|
87
|
+
- spec/cluster_spec.rb
|
88
|
+
- spec/config_spec/app/config/config.yml
|
89
|
+
- spec/config_spec.rb
|
90
|
+
- spec/dependency_spec.rb
|
91
|
+
- spec/service_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
76
93
|
has_rdoc: true
|
77
94
|
homepage: http://github.com/alexeypetrushin/cluster_management
|
78
95
|
licenses: []
|