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 CHANGED
@@ -2,7 +2,7 @@ require 'rake_ext'
2
2
 
3
3
  project(
4
4
  name: "cluster_management",
5
- version: "0.7",
5
+ version: "0.7.1",
6
6
  summary: "Simple Cluster Management Tools",
7
7
 
8
8
  author: "Alexey Petrushin",
@@ -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, *a, &b
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? ? Box.new(host.to_s, config.ssh.to_h) : Box.new(host.to_s)
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
- def configure runtime_dir
43
- config.merge_file! "#{runtime_dir}/config/config.yml"
44
- config.set! :config_path, "#{runtime_dir}/config"
45
-
46
- r = {}
47
- config.boxes!.to_h.each do |box, tags|
48
- tags.each do |tag|
49
- (r[tag.to_sym] ||= []) << box
50
- end
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 < SafeHash
2
- def merge_file! file_path
3
- raise("config file must have .yml extension (#{file_path})!") unless file_path.end_with? '.yml'
4
- data = ::YAML.load_file file_path
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.merge! data, override: true
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
@@ -3,5 +3,6 @@ gem 'tilt'
3
3
  if respond_to? :fake_gem
4
4
  fake_gem 'ruby_ext'
5
5
  fake_gem 'vos'
6
- fake_gem 'class_loader'
6
+ fake_gem 'vfs'
7
+ fake_gem 'class_loader'
7
8
  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 &b
45
- if b
46
- boxes.each &b
47
- else
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 single_box
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
- # Exception.filters = []
29
+ Exception.filters = []
30
30
 
31
31
  Exception.class_eval do
32
32
  alias_method :set_backtrace_without_filter, :set_backtrace
@@ -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; ::CLUSTER end
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 cluster management tools
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 extremely easy**, there are only 3 methods.
4
+ **It's very easy**, there are only a 3 concept - Box, Cluster and Service.
5
5
 
6
- You probably already familiar with Rake and have it in Your project, and because ClusterManagement is just a small Rake
7
- addon it should be easy to add and get started with it.
6
+ Usage:
8
7
 
9
- It's ssh-agnostic and has no extra dependencies. You can use whatever ssh-tool you like (even pure Net::SSH / Net::SFTP),
10
- samples below are done by using [Virtual Operating System][vos] and [Virtual File System][vfs] tools.
8
+ - package installation, dependencies, versioning
9
+ - process management, start/stop services
10
+ - deplyment
11
11
 
12
- ## BoxTask Management
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
- Define your packages, they are just rake tasks, so you probably know how to work with them:
14
+ ## Core Concepts
15
15
 
16
- desc 'ruby 1.9.2'
17
- package ruby: :system_tools do
18
- apply_once do
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
- box.tmp do |tmp|
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
- src_dir = tmp[ruby_name]
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
- box.home('.gemrc').write! "gem: --no-ri --no-rdoc\n"
24
+ ![Deployment Scheme][deployment_scheme]
32
25
 
33
- bindir = "#{installation_dir}/bin"
34
- unless box.env_file.content =~ /PATH.*#{bindir}/
35
- box.env_file.append %(\nexport PATH="$PATH:#{bindir}"\n)
36
- box.reload_env
37
- end
38
- end
39
- verify{box.bash('ruby -v') =~ /ruby 1.9.2/}
40
- end
41
-
42
- Or you can use a little more explicit notation with custom :applied? logic (:apply_once is a shortcut for :applied? & :after_applying):
43
-
44
- package :ruby do
45
- applied?{box.has_mark? :ruby}
46
- apply do
47
- ...
48
- end
49
- after_applying{box.mark :ruby}
50
- end
51
-
52
- Let's define another package:
53
-
54
- package rails: :ruby, version: 3 do
55
- apply_once do
56
- box.bash 'gem install rails'
57
- end
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
- It's understands dependencies, so the :rails package will apply :ruby before applying itself.
61
-
62
- It checks if the package already has been applied to box, so you can evolve your configuration and apply it multiple times,
63
- it will apply only missing packages (or drop the applied? clause and it will be applied every run). It allows you
64
- to use **iterative development**, you don't need to write all the config at once, do it by small steps, adding one package after another.
65
-
66
- You can also use versioning to update already installed packages - if You change version it will be reapplied next run.
67
- And by the way, the box.mark ... is just an example check, you can use anything there.
68
-
69
- And, last step - define (I intentionally leave implementation of this method to You, it's very specific to Your environment)
70
- to what machines it should be applied:
71
-
72
- module ClusterManagement
73
- def self.boxes
74
- unless @boxes
75
- host = ENV['host'] || raise(":host not defined!")
76
- box = Vos::Box.new host: host, ssh: config.ssh!.to_h
77
- box.open
78
-
79
- @boxes = [box]
80
- end
81
- @boxes
82
- end
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
- Now, you can press the enter:
86
-
87
- $ rake os:rails host=myapp.com
88
-
89
- and box_task will do all the job of installing and configuring your cluster boxes, and prints you something like that
90
- (it's a sample output of some of my own box, you can see config details here [my_cluster][my_cluster]):
91
-
92
- $ rake app_server host=universal.xxx.com
93
- applying 'basic:os:5' to '<Box: universal.xxx.com>'
94
- applying 'basic:apt' to '<Box: universal.xxx.com>'
95
- applying 'basic:system_tools' to '<Box: universal.xxx.com>'
96
- applying 'basic:ruby' to '<Box: universal.xxx.com>'
97
- building ... done
98
- updating path ... done
99
- applying 'basic:git' to '<Box: universal.xxx.com>'
100
- applying 'basic:security:6' to '<Box: universal.xxx.com>'
101
- applying 'basic:manual_management:2' to '<Box: universal.xxx.com>'
102
- applying 'app_server:fake_gem:2' to '<Box: universal.xxx.com>'
103
- applying 'app_server:custom_ruby:3' to '<Box: universal.xxx.com>'
104
-
105
- You can also use standard Rake -T command to see docs (it's also from my config, details are here [my_cluster][my_cluster]):
106
-
107
- $ rake -T
108
- rake app_server # app server
109
- rake app_server:custom_ruby # custom ruby (with encoding globally set to unicode and enabled fake_gem hack)
110
- rake app_server:fake_gem # fake_gem
111
- rake basic # Box with basic packages installed
112
- rake basic:apt # apt
113
- rake basic:git # git
114
- rake basic:manual_management # Makes box handy for manual management
115
- rake basic:os # Checks OS version and add some very basic stuff
116
- rake basic:ruby # ruby
117
- rake basic:security # security
118
- rake basic:system_tools # System tools, mainly for build support
119
- rake db # db
120
- rake db:mongodb # MongoDB
121
-
122
- ## Service Management
123
-
124
- [add details here]
125
-
126
- ## Deployment
127
-
128
- [add more details here]
129
-
130
- **You can use it also for deployment**, exactly the same way, configure it the way you like, it's just rake
131
- tasks.
132
-
133
- # Temporarry stuff, don't bother to read it
134
-
135
- - small
136
- - uses well known tools (rake and anytingh ssh-enabled)
137
- - support iterative development
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
@@ -0,0 +1,7 @@
1
+ handy_scheme:
2
+ 'web1.app.com': ['app', 'ruby']
3
+ 'web2.app.com': ['app', 'ruby']
4
+ 'db.app.com': ['db']
5
+
6
+ nginx:
7
+ port: 80
@@ -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
@@ -0,0 +1,4 @@
1
+ require 'rspec_ext'
2
+ require 'cluster_management'
3
+
4
+ Cluster, Config = ClusterManagement::Cluster, ClusterManagement::Config
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: '0.7'
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-06-24 00:00:00.000000000 +04:00
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: &2738130 !ruby/object:Gem::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: *2738130
25
+ version_requirements: *3013040
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: ruby_ext
28
- requirement: &2737850 !ruby/object:Gem::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: *2737850
36
+ version_requirements: *3012690
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: vos
39
- requirement: &2737650 !ruby/object:Gem::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: *2737650
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: &2737450 !ruby/object:Gem::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: *2737450
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: []