motherbrain 0.0.0.placeholder → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +9 -0
  5. data/CHANGELOG.md +196 -0
  6. data/COMMANDS.md +9 -0
  7. data/CONTRIBUTING.md +24 -0
  8. data/Dockerfile +26 -0
  9. data/Gemfile +60 -2
  10. data/Guardfile +30 -0
  11. data/LICENSE +10 -0
  12. data/MANIFESTS.md +90 -0
  13. data/OPERATORS_GUIDE.md +195 -0
  14. data/PLUGINS.md +268 -0
  15. data/README.md +304 -16
  16. data/Thorfile +123 -0
  17. data/VAGRANT.md +116 -0
  18. data/bin/boot +9 -0
  19. data/bin/mb +5 -0
  20. data/bin/mbsrv +5 -0
  21. data/config.json +32 -0
  22. data/features/cli/bootstrap_command/configurable_scripts.feature +32 -0
  23. data/features/cli/configure_command.feature +57 -0
  24. data/features/cli/environment_command/create_command.feature +22 -0
  25. data/features/cli/environment_command/destroy_command.feature +33 -0
  26. data/features/cli/environment_command/from_command.feature +29 -0
  27. data/features/cli/environment_command/list_command.feature +0 -0
  28. data/features/cli/node_limiting.feature +47 -0
  29. data/features/cli/plugin_command/list_command.feature +46 -0
  30. data/features/cli/service_command/service_command.feature +21 -0
  31. data/features/cli/template_command.feature +10 -0
  32. data/features/cli/validate_config.feature +46 -0
  33. data/features/step_definitions/bootstrap_steps.rb +57 -0
  34. data/features/step_definitions/chef_server_steps.rb +3 -0
  35. data/features/step_definitions/configuration_steps.rb +18 -0
  36. data/features/step_definitions/core_cli_steps.rb +33 -0
  37. data/features/step_definitions/environment_steps.rb +43 -0
  38. data/features/step_definitions/node_steps.rb +3 -0
  39. data/features/step_definitions/plugin_steps.rb +15 -0
  40. data/features/step_definitions/template_steps.rb +7 -0
  41. data/features/support/env.rb +68 -0
  42. data/lib/mb/api.rb +8 -0
  43. data/lib/mb/api/application.rb +7 -0
  44. data/lib/mb/api/endpoint.rb +5 -0
  45. data/lib/mb/api/helpers.rb +38 -0
  46. data/lib/mb/api/v1.rb +56 -0
  47. data/lib/mb/api/v1/config_endpoint.rb +12 -0
  48. data/lib/mb/api/v1/environments_endpoint.rb +174 -0
  49. data/lib/mb/api/v1/jobs_endpoint.rb +31 -0
  50. data/lib/mb/api/v1/plugins_endpoint.rb +90 -0
  51. data/lib/mb/api/validators.rb +5 -0
  52. data/lib/mb/api/validators/sem_ver.rb +18 -0
  53. data/lib/mb/application.rb +148 -0
  54. data/lib/mb/berkshelf.rb +50 -0
  55. data/lib/mb/bootstrap.rb +9 -0
  56. data/lib/mb/bootstrap/manager.rb +250 -0
  57. data/lib/mb/bootstrap/manifest.rb +131 -0
  58. data/lib/mb/bootstrap/routine.rb +199 -0
  59. data/lib/mb/bootstrap/template.rb +73 -0
  60. data/lib/mb/bootstrap/worker.rb +227 -0
  61. data/lib/mb/chef.rb +6 -0
  62. data/lib/mb/chef/config.rb +69 -0
  63. data/lib/mb/chef/run_list_item.rb +115 -0
  64. data/lib/mb/chef_mutex.rb +304 -0
  65. data/lib/mb/clean_room_base.rb +39 -0
  66. data/lib/mb/cli.rb +50 -0
  67. data/lib/mb/cli/base.rb +51 -0
  68. data/lib/mb/cli/shell.rb +29 -0
  69. data/lib/mb/cli/shell/basic.rb +11 -0
  70. data/lib/mb/cli/shell/color.rb +11 -0
  71. data/lib/mb/cli/shell/ext.rb +41 -0
  72. data/lib/mb/cli/sub_command.rb +95 -0
  73. data/lib/mb/cli/sub_command/component.rb +56 -0
  74. data/lib/mb/cli/sub_command/plugin.rb +232 -0
  75. data/lib/mb/cli_client.rb +178 -0
  76. data/lib/mb/cli_gateway.rb +426 -0
  77. data/lib/mb/cli_gateway/sub_commands.rb +3 -0
  78. data/lib/mb/cli_gateway/sub_commands/environment.rb +124 -0
  79. data/lib/mb/cli_gateway/sub_commands/plugin.rb +148 -0
  80. data/lib/mb/command.rb +88 -0
  81. data/lib/mb/command_invoker.rb +235 -0
  82. data/lib/mb/command_invoker/worker.rb +40 -0
  83. data/lib/mb/command_runner.rb +233 -0
  84. data/lib/mb/component.rb +245 -0
  85. data/lib/mb/config.rb +275 -0
  86. data/lib/mb/config_manager.rb +75 -0
  87. data/lib/mb/console.rb +35 -0
  88. data/lib/mb/cookbook_metadata.rb +73 -0
  89. data/lib/mb/core_ext.rb +3 -0
  90. data/lib/mb/core_ext/dir.rb +37 -0
  91. data/lib/mb/core_ext/enumerable.rb +48 -0
  92. data/lib/mb/core_ext/file.rb +24 -0
  93. data/lib/mb/core_ext/signal.rb +11 -0
  94. data/lib/mb/environment_manager.rb +195 -0
  95. data/lib/mb/error_handler.rb +212 -0
  96. data/lib/mb/errors.rb +693 -0
  97. data/lib/mb/file_system.rb +60 -0
  98. data/lib/mb/file_system/tempfile.rb +25 -0
  99. data/lib/mb/gear.rb +154 -0
  100. data/lib/mb/gears.rb +8 -0
  101. data/lib/mb/gears/dynamic_service.rb +218 -0
  102. data/lib/mb/gears/jmx.rb +24 -0
  103. data/lib/mb/gears/jmx/action.rb +46 -0
  104. data/lib/mb/gears/mysql.rb +20 -0
  105. data/lib/mb/gears/mysql/action.rb +190 -0
  106. data/lib/mb/gears/service.rb +163 -0
  107. data/lib/mb/gears/service/action.rb +58 -0
  108. data/lib/mb/gears/service/action_runner.rb +161 -0
  109. data/lib/mb/grape_ext.rb +3 -0
  110. data/lib/mb/grape_ext/endpoint.rb +13 -0
  111. data/lib/mb/group.rb +143 -0
  112. data/lib/mb/job.rb +183 -0
  113. data/lib/mb/job/state_machine.rb +34 -0
  114. data/lib/mb/job/states.rb +46 -0
  115. data/lib/mb/job_manager.rb +96 -0
  116. data/lib/mb/job_record.rb +67 -0
  117. data/lib/mb/job_ticket.rb +25 -0
  118. data/lib/mb/lock_manager.rb +116 -0
  119. data/lib/mb/logging.rb +134 -0
  120. data/lib/mb/logging/basic_format.rb +31 -0
  121. data/lib/mb/manifest.rb +128 -0
  122. data/lib/mb/mixin.rb +3 -0
  123. data/lib/mb/mixin/attribute_setting.rb +265 -0
  124. data/lib/mb/mixin/coded_exit.rb +49 -0
  125. data/lib/mb/mixin/locks.rb +54 -0
  126. data/lib/mb/mixin/services.rb +100 -0
  127. data/lib/mb/node_filter.rb +97 -0
  128. data/lib/mb/node_querier.rb +527 -0
  129. data/lib/mb/plugin.rb +300 -0
  130. data/lib/mb/plugin_manager.rb +589 -0
  131. data/lib/mb/provisioner.rb +186 -0
  132. data/lib/mb/provisioner/manager.rb +213 -0
  133. data/lib/mb/provisioner/manifest.rb +125 -0
  134. data/lib/mb/provisioner/provision_data.rb +96 -0
  135. data/lib/mb/provisioners.rb +5 -0
  136. data/lib/mb/provisioners/aws.rb +395 -0
  137. data/lib/mb/rest_gateway.rb +72 -0
  138. data/lib/mb/ridley_ext.rb +5 -0
  139. data/lib/mb/ridley_ext/cookbook_object.rb +15 -0
  140. data/lib/mb/srv_ctl.rb +183 -0
  141. data/lib/mb/test.rb +104 -0
  142. data/lib/mb/thor_ext.rb +49 -0
  143. data/lib/mb/upgrade.rb +6 -0
  144. data/lib/mb/upgrade/manager.rb +85 -0
  145. data/lib/mb/upgrade/worker.rb +149 -0
  146. data/lib/mb/version.rb +1 -1
  147. data/lib/motherbrain.rb +166 -2
  148. data/man/man_helper.rb +81 -0
  149. data/man/mb.1 +494 -0
  150. data/man/mb.1.html +300 -0
  151. data/man/mb.1.ronn.erb +62 -0
  152. data/motherbrain.gemspec +56 -20
  153. data/scripts/node_name.rb +14 -0
  154. data/spec/fixtures/cb_metadata.json +7 -0
  155. data/spec/fixtures/cb_metadata.rb +14 -0
  156. data/spec/fixtures/fake_id_rsa +27 -0
  157. data/spec/fixtures/fake_key.pem +27 -0
  158. data/spec/fixtures/myface-0.1.0/metadata.rb +14 -0
  159. data/spec/fixtures/myface-0.1.0/motherbrain.rb +0 -0
  160. data/spec/fixtures/test_env.json +15 -0
  161. data/spec/spec_helper.rb +67 -0
  162. data/spec/support/actor_mocking.rb +7 -0
  163. data/spec/support/berkshelf.rb +24 -0
  164. data/spec/support/chef_server.rb +102 -0
  165. data/spec/support/doubles.rb +11 -0
  166. data/spec/support/klass.rb +10 -0
  167. data/spec/support/matchers/each.rb +12 -0
  168. data/spec/support/matchers/error_codes.rb +5 -0
  169. data/spec/support/matchers/exit_codes.rb +57 -0
  170. data/spec/support/matchers/jobs.rb +11 -0
  171. data/spec/support/spec_helpers.rb +145 -0
  172. data/spec/unit/mb/api/application_spec.rb +11 -0
  173. data/spec/unit/mb/api/helpers_spec.rb +5 -0
  174. data/spec/unit/mb/api/v1/config_endpoint_spec.rb +19 -0
  175. data/spec/unit/mb/api/v1/environments_endpoint_spec.rb +71 -0
  176. data/spec/unit/mb/api/v1/jobs_endpoint_spec.rb +24 -0
  177. data/spec/unit/mb/api/v1/plugins_endpoint_spec.rb +298 -0
  178. data/spec/unit/mb/api/v1_spec.rb +37 -0
  179. data/spec/unit/mb/api/validators/sem_ver_spec.rb +5 -0
  180. data/spec/unit/mb/application_spec.rb +19 -0
  181. data/spec/unit/mb/berkshelf_spec.rb +38 -0
  182. data/spec/unit/mb/bootstrap/manager_spec.rb +347 -0
  183. data/spec/unit/mb/bootstrap/manifest_spec.rb +333 -0
  184. data/spec/unit/mb/bootstrap/routine_spec.rb +393 -0
  185. data/spec/unit/mb/bootstrap/template_spec.rb +100 -0
  186. data/spec/unit/mb/bootstrap/worker_spec.rb +194 -0
  187. data/spec/unit/mb/chef/config_spec.rb +33 -0
  188. data/spec/unit/mb/chef/run_list_item_spec.rb +34 -0
  189. data/spec/unit/mb/chef_mutex_spec.rb +314 -0
  190. data/spec/unit/mb/clean_room_base_spec.rb +31 -0
  191. data/spec/unit/mb/cli/base_spec.rb +43 -0
  192. data/spec/unit/mb/cli/shell/basic_spec.rb +5 -0
  193. data/spec/unit/mb/cli/shell/color_spec.rb +5 -0
  194. data/spec/unit/mb/cli/shell/ext_spec.rb +11 -0
  195. data/spec/unit/mb/cli/shell_spec.rb +38 -0
  196. data/spec/unit/mb/cli/sub_command/base_spec.rb +102 -0
  197. data/spec/unit/mb/cli/sub_command/component_spec.rb +5 -0
  198. data/spec/unit/mb/cli/sub_command/plugin_spec.rb +91 -0
  199. data/spec/unit/mb/cli/sub_command_spec.rb +43 -0
  200. data/spec/unit/mb/cli/ui.rb +0 -0
  201. data/spec/unit/mb/cli_client_spec.rb +51 -0
  202. data/spec/unit/mb/cli_gateway_spec.rb +386 -0
  203. data/spec/unit/mb/command_invoker/worker_spec.rb +43 -0
  204. data/spec/unit/mb/command_invoker_spec.rb +230 -0
  205. data/spec/unit/mb/command_runner_spec.rb +299 -0
  206. data/spec/unit/mb/command_spec.rb +76 -0
  207. data/spec/unit/mb/component_spec.rb +185 -0
  208. data/spec/unit/mb/config_manager_spec.rb +31 -0
  209. data/spec/unit/mb/config_spec.rb +408 -0
  210. data/spec/unit/mb/cookbook_metadata_spec.rb +89 -0
  211. data/spec/unit/mb/core_ext/dir_spec.rb +92 -0
  212. data/spec/unit/mb/core_ext/enumerable_spec.rb +104 -0
  213. data/spec/unit/mb/core_ext/file_spec.rb +58 -0
  214. data/spec/unit/mb/core_ext/signal_spec.rb +24 -0
  215. data/spec/unit/mb/environment_manager_spec.rb +166 -0
  216. data/spec/unit/mb/error_handler_spec.rb +173 -0
  217. data/spec/unit/mb/errors_spec.rb +132 -0
  218. data/spec/unit/mb/file_system/tempfile_spec.rb +14 -0
  219. data/spec/unit/mb/file_system_spec.rb +69 -0
  220. data/spec/unit/mb/gear_spec.rb +125 -0
  221. data/spec/unit/mb/gears/dynamic_service_spec.rb +187 -0
  222. data/spec/unit/mb/gears/jmx/action_spec.rb +34 -0
  223. data/spec/unit/mb/gears/jmx_spec.rb +32 -0
  224. data/spec/unit/mb/gears/mysql/action_spec.rb +118 -0
  225. data/spec/unit/mb/gears/mysql_spec.rb +21 -0
  226. data/spec/unit/mb/gears/service/action_runner_spec.rb +182 -0
  227. data/spec/unit/mb/gears/service/action_spec.rb +44 -0
  228. data/spec/unit/mb/gears/service_spec.rb +124 -0
  229. data/spec/unit/mb/group_spec.rb +280 -0
  230. data/spec/unit/mb/job_manager_spec.rb +56 -0
  231. data/spec/unit/mb/job_record_spec.rb +60 -0
  232. data/spec/unit/mb/job_spec.rb +201 -0
  233. data/spec/unit/mb/locks_manager_spec.rb +88 -0
  234. data/spec/unit/mb/logging_spec.rb +133 -0
  235. data/spec/unit/mb/manifest_spec.rb +105 -0
  236. data/spec/unit/mb/mixin/attribute_setting_spec.rb +180 -0
  237. data/spec/unit/mb/mixin/coded_exit_spec.rb +25 -0
  238. data/spec/unit/mb/mixin/locks_spec.rb +32 -0
  239. data/spec/unit/mb/mixin/services_spec.rb +75 -0
  240. data/spec/unit/mb/node_filter_spec.rb +86 -0
  241. data/spec/unit/mb/node_querier_spec.rb +532 -0
  242. data/spec/unit/mb/plugin_manager_spec.rb +724 -0
  243. data/spec/unit/mb/plugin_spec.rb +247 -0
  244. data/spec/unit/mb/provisioner/manager_spec.rb +141 -0
  245. data/spec/unit/mb/provisioner/manifest_spec.rb +182 -0
  246. data/spec/unit/mb/provisioner/provision_data_spec.rb +113 -0
  247. data/spec/unit/mb/provisioner_spec.rb +251 -0
  248. data/spec/unit/mb/provisioners/aws_spec.rb +392 -0
  249. data/spec/unit/mb/provisioners/environment_factory_spec.rb +108 -0
  250. data/spec/unit/mb/rest_gateway_spec.rb +13 -0
  251. data/spec/unit/mb/ridley_ext/cookbook_object_spec.rb +105 -0
  252. data/spec/unit/mb/srv_ctl_spec.rb +142 -0
  253. data/spec/unit/mb/upgrade/manager_spec.rb +37 -0
  254. data/spec/unit/mb/upgrade/worker_spec.rb +219 -0
  255. data/spec/unit/motherbrain_spec.rb +58 -0
  256. data/templates/bootstrap.json +8 -0
  257. data/templates/motherbrain.rb +44 -0
  258. metadata +694 -15
  259. data/Rakefile +0 -1
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe MB::API::Validators::SemVer do
4
+ pending
5
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe MB::Application do
4
+ describe "ClassMethods" do
5
+ subject { described_class }
6
+
7
+ describe "::registry" do
8
+ it "returns an instance of Celluloid::Registry" do
9
+ subject.registry.should be_a(Celluloid::Registry)
10
+ end
11
+ end
12
+
13
+ describe "::config" do
14
+ it "returns an instance of MB::Config" do
15
+ subject.config.should be_a(MB::Config)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe MB::Berkshelf do
4
+ describe "::cookbooks_path" do
5
+ subject { described_class.cookbooks_path }
6
+
7
+ it "returns a Pathname" do
8
+ subject.should be_a(Pathname)
9
+ end
10
+
11
+ it "is in the Berkshelf path" do
12
+ subject.to_s.should include(MB::Berkshelf.path.to_s)
13
+ end
14
+ end
15
+
16
+ describe "::default_path" do
17
+ subject { described_class.default_path }
18
+
19
+ it "returns a String" do
20
+ subject.should be_a(String)
21
+ end
22
+
23
+ it "returns the value of ENV['BERKSHELF_PATH'] if present" do
24
+ target = "/tmp/berkshelf"
25
+ ENV.stub(:[]).with("BERKSHELF_PATH").and_return(target)
26
+
27
+ subject.should eql(target)
28
+ end
29
+ end
30
+
31
+ describe "::path" do
32
+ subject { described_class.path }
33
+
34
+ it "returns a Pathname" do
35
+ subject.should be_a(Pathname)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,347 @@
1
+ require 'spec_helper'
2
+
3
+ describe MB::Bootstrap::Manager do
4
+ subject { manager }
5
+ let(:manager) { described_class.new }
6
+
7
+ let(:cookbook_metadata) {
8
+ MB::CookbookMetadata.from_file(fixtures_path.join('cb_metadata.rb'))
9
+ }
10
+
11
+ let(:plugin) {
12
+ MB::Plugin.new(cookbook_metadata) do
13
+ component "activemq" do
14
+ group "master"
15
+ group "slave"
16
+ end
17
+
18
+ component "nginx" do
19
+ group "master"
20
+ end
21
+
22
+ stack_order do
23
+ async do
24
+ bootstrap("activemq::master")
25
+ bootstrap("activemq::slave")
26
+ end
27
+
28
+ bootstrap("nginx::master")
29
+ end
30
+ end
31
+ }
32
+
33
+ let(:manifest) {
34
+ MB::Bootstrap::Manifest.new({
35
+ nodes: [
36
+ {
37
+ groups: ["activemq::master"],
38
+ hosts: [
39
+ "amq1.riotgames.com",
40
+ "amq2.riotgames.com"
41
+ ]
42
+ },
43
+ {
44
+ groups: ["activemq::slave"],
45
+ hosts: [
46
+ "amqs1.riotgames.com",
47
+ "amqs2.riotgames.com"
48
+ ]
49
+ },
50
+ {
51
+ groups: ["nginx::master"],
52
+ hosts: [
53
+ "nginx1.riotgames.com"
54
+ ]
55
+ }
56
+ ]
57
+ })
58
+ }
59
+
60
+ let(:environment) { "test" }
61
+ let(:server_url) { MB::Application.config.chef.api_url }
62
+
63
+ before do
64
+ stub_request(:get, File.join(server_url, "nodes")).
65
+ to_return(status: 200, body: {})
66
+ stub_request(:get, File.join(server_url, "environments/test")).
67
+ to_return(status: 200, body: {})
68
+
69
+ manager.stub(async: manager)
70
+ end
71
+
72
+ describe "#async_bootstrap" do
73
+ it "delegates asynchronously to {#bootstrap}" do
74
+ options = double('options')
75
+ manager.should_receive(:async).with(
76
+ :bootstrap,
77
+ kind_of(MB::Job),
78
+ environment,
79
+ manifest,
80
+ plugin,
81
+ options
82
+ )
83
+
84
+ manager.async_bootstrap(environment, manifest, plugin, options)
85
+ end
86
+
87
+ it "returns a JobRecord" do
88
+ manager.async_bootstrap(environment, manifest, plugin).should be_a(MB::JobRecord)
89
+ end
90
+ end
91
+
92
+ describe "#bootstrap" do
93
+ let(:job) { MB::Job.new(:bootstrap) }
94
+ let(:options) { Hash.new }
95
+
96
+ before(:each) do
97
+ manager.stub(concurrent_bootstrap: {})
98
+ end
99
+
100
+ let(:run) { manager.bootstrap(job, environment, manifest, plugin, options) }
101
+
102
+ context "when the validatiom key is not on disk" do
103
+ it "fails the job before locking the environment" do
104
+ config_manager.stub_chain(:config).and_return(chef: {validation_key: '/this/file/doesnt/exist.pem'})
105
+ manager.should_not_receive(:chef_synchronize)
106
+ job.should_receive(:report_failure)
107
+
108
+ run
109
+ end
110
+ end
111
+
112
+ context "when the environment cannot be found" do
113
+ before(:each) do
114
+ manager.stub_chain(:chef_connection, :environment, :find).with(environment).and_return(nil)
115
+ end
116
+
117
+ it "sets the job to failed before locking the environment" do
118
+ manager.should_not_receive(:chef_synchronize)
119
+ job.should_receive(:report_failure)
120
+
121
+ run
122
+ end
123
+ end
124
+
125
+ context "when the given bootstrap manifest is invalid" do
126
+ it "sets the job to failed before locking the environment" do
127
+ manifest.should_receive(:validate!).with(plugin).and_raise(MB::InvalidBootstrapManifest)
128
+ manager.should_not_receive(:chef_synchronize)
129
+ job.should_receive(:report_failure)
130
+
131
+ run
132
+ end
133
+ end
134
+
135
+ context "when :environment_attributes_file is passed as an option" do
136
+ let(:filepath) { double }
137
+
138
+ before do
139
+ options[:environment_attributes_file] = filepath
140
+ end
141
+
142
+ it "sets environment attributes on the environment with the contents of the file" do
143
+ manager.should_receive(:set_environment_attributes_from_file).with(environment, filepath)
144
+ run
145
+ end
146
+
147
+ context "when the environment attributes file is invalid" do
148
+ it "sets the job to failed before running #concurrent_bootstrap" do
149
+ manager.should_receive(:set_environment_attributes_from_file).and_raise(MB::InvalidAttributesFile)
150
+ manager.should_not_receive(:concurrent_bootstrap)
151
+ job.should_receive(:report_failure)
152
+
153
+ run
154
+ end
155
+ end
156
+ end
157
+
158
+ context "when :environment_attributes_file is not passed as an option" do
159
+ before do
160
+ options[:environment_attributes_file] = nil
161
+ end
162
+
163
+ it "does not set the environment attributes on the environment" do
164
+ manager.should_not_receive(:set_environment_attributes_file)
165
+ run
166
+ end
167
+ end
168
+
169
+ context "when all nodes bootstrap successfully" do
170
+ let(:host_one) { "euca-10-20-37-171.eucalyptus.cloud.riotgames.com" }
171
+ let(:host_two) { "euca-10-20-37-172.eucalyptus.cloud.riotgames.com" }
172
+
173
+ let(:response) do
174
+ {
175
+ host_one => {
176
+ groups: ["activemq::master"],
177
+ result: {
178
+ status: :ok,
179
+ message: "",
180
+ bootstrap_type: :full
181
+ }
182
+ },
183
+ host_two => {
184
+ groups: ["activemq::master"],
185
+ result: {
186
+ status: :ok,
187
+ message: "",
188
+ bootstrap_type: :partial
189
+ }
190
+ }
191
+ }
192
+ end
193
+
194
+ let(:manifest) do
195
+ MB::Bootstrap::Manifest.new(nodes: [
196
+ {
197
+ groups: ["activemq::master"],
198
+ hosts: [ host_one, host_two ]
199
+ }
200
+ ])
201
+ end
202
+
203
+ it "sets the job to success" do
204
+ manager.should_receive(:concurrent_bootstrap).and_return(response)
205
+ job.should_receive(:report_success)
206
+
207
+ run
208
+ end
209
+ end
210
+
211
+ context "when there is an error in one or more nodes bootstrapped" do
212
+ let(:host_one) { "euca-10-20-37-171.eucalyptus.cloud.riotgames.com" }
213
+ let(:host_two) { "euca-10-20-37-172.eucalyptus.cloud.riotgames.com" }
214
+
215
+ let(:response) do
216
+ {
217
+ host_one => {
218
+ groups: ["activemq::master"],
219
+ result: {
220
+ status: :error,
221
+ message: "something helpful",
222
+ bootstrap_type: :full
223
+ }
224
+ },
225
+ host_two => {
226
+ groups: ["activemq::master"],
227
+ result: {
228
+ status: :ok,
229
+ message: "",
230
+ bootstrap_type: :partial
231
+ }
232
+ }
233
+ }
234
+ end
235
+
236
+ let(:manifest) do
237
+ MB::Bootstrap::Manifest.new(nodes: [
238
+ {
239
+ groups: ["activemq::master"],
240
+ hosts: [ host_one, host_two ]
241
+ }
242
+ ])
243
+ end
244
+
245
+ it "sets the job to success" do
246
+ manager.should_receive(:concurrent_bootstrap).and_return(response)
247
+ job.should_receive(:report_failure)
248
+
249
+ run
250
+ end
251
+ end
252
+ end
253
+
254
+ describe "#concurrent_bootstrap" do
255
+ let(:job) { MB::Job.new(:bootstrap) }
256
+ let(:tasks) { Array.new }
257
+ let(:instructions) { MB::Bootstrap::Routine.map_instructions(tasks, manifest) }
258
+ let(:worker_pool) { double('worker-pool') }
259
+ let(:result) { manager.concurrent_bootstrap(job, manifest, instructions) }
260
+
261
+ before { subject.stub(worker_pool: worker_pool) }
262
+
263
+ context "with a manifest containing a node group with two groups and a task for each" do
264
+ let(:tasks) do
265
+ [
266
+ MB::Bootstrap::Routine::Task.new("app_server::default",
267
+ run_list: [ "recipe[one]", "recipe[two]" ],
268
+ chef_attributes: { deep: { one: "val" } }
269
+ ),
270
+ MB::Bootstrap::Routine::Task.new("database_master::default",
271
+ run_list: [ "recipe[three]" ],
272
+ chef_attributes: { deep: { two: "val" } }
273
+ )
274
+ ]
275
+ end
276
+
277
+ let(:host_one) { "euca-10-20-37-171.eucalyptus.cloud.riotgames.com" }
278
+ let(:host_two) { "euca-10-20-37-172.eucalyptus.cloud.riotgames.com" }
279
+
280
+ let(:response_one) do
281
+ double('future-one', value: {
282
+ node: host_one,
283
+ status: :ok,
284
+ message: "",
285
+ bootstrap_type: :full
286
+ })
287
+ end
288
+
289
+ let(:response_two) do
290
+ double('future-two', value: {
291
+ node: host_two,
292
+ status: :error,
293
+ message: "client verification error",
294
+ bootstrap_type: :partial
295
+ })
296
+ end
297
+
298
+ let(:manifest) do
299
+ MB::Bootstrap::Manifest.new(
300
+ nodes: [
301
+ {
302
+ groups: ["app_server::default", "database_master::default"],
303
+ hosts: [ host_one, host_two ]
304
+ }
305
+ ]
306
+ )
307
+ end
308
+
309
+ it "bootstraps a node only one time" do
310
+ worker_pool.should_receive(:future).with(:run, host_one, anything).once.and_return(response_one)
311
+ worker_pool.should_receive(:future).with(:run, host_two, anything).once.and_return(response_two)
312
+ result
313
+ end
314
+
315
+ it "bootstraps each node with a merged run list from each task" do
316
+ options = { chef_attributes: anything, run_list: ["recipe[one]", "recipe[two]", "recipe[three]"]}
317
+ worker_pool.should_receive(:future).with(:run, host_one, options).once.and_return(response_one)
318
+ worker_pool.should_receive(:future).with(:run, host_two, options).once.and_return(response_two)
319
+ result
320
+ end
321
+
322
+ it "bootstraps each node with merged chef attributes from each task" do
323
+ chef_attributes = Hashie::Mash.new(deep: { one: "val", two: "val" })
324
+ options = { chef_attributes: chef_attributes, run_list: anything }
325
+ worker_pool.should_receive(:future).with(:run, host_one, options).once.and_return(response_one)
326
+ worker_pool.should_receive(:future).with(:run, host_two, options).once.and_return(response_two)
327
+ result
328
+ end
329
+ end
330
+
331
+ context "when there are no nodes in the manifest" do
332
+ let(:manifest) { MB::Bootstrap::Manifest.new }
333
+
334
+ it "returns an empty Hash" do
335
+ expect(result).to be_empty
336
+ end
337
+ end
338
+
339
+ context "given an empty array of tasks" do
340
+ let(:tasks) { Array.new }
341
+
342
+ it "returns an empty Hash" do
343
+ expect(result).to be_empty
344
+ end
345
+ end
346
+ end
347
+ end
@@ -0,0 +1,333 @@
1
+ require 'spec_helper'
2
+
3
+ describe MB::Bootstrap::Manifest do
4
+ subject { manifest }
5
+ let(:manifest) { described_class.new(attributes) }
6
+
7
+ let(:attributes) {
8
+ {
9
+ nodes: [
10
+ {
11
+ groups: ["activemq::master"],
12
+ hosts: ["amq1.riotgames.com"]
13
+ },
14
+
15
+ {
16
+ groups: ["nginx::master"],
17
+ hosts: ["nginx1.riotgames.com"]
18
+ },
19
+ ]
20
+ }
21
+ }
22
+ describe "#hosts_for_group" do
23
+ subject { manifest.hosts_for_group(group) }
24
+
25
+ let(:group) { "activemq::master" }
26
+
27
+ it { should == manifest[:nodes].first[:hosts] }
28
+ end
29
+
30
+ describe "#validate!" do
31
+ subject(:validate!) { manifest.validate!(plugin) }
32
+
33
+ let(:plugin) {
34
+ metadata = MB::CookbookMetadata.new do
35
+ name "pvpnet"
36
+ version "1.2.3"
37
+ end
38
+
39
+ MB::Plugin.new(metadata) do
40
+ component "activemq" do
41
+ group "master"
42
+ end
43
+
44
+ component "nginx" do
45
+ group "master"
46
+ end
47
+
48
+ stack_order do
49
+ bootstrap("activemq::master")
50
+ bootstrap("nginx::master")
51
+ end
52
+ end
53
+ }
54
+
55
+ it "does not raise if the manifest is well formed and contains only node groups from the given plugin" do
56
+ expect { validate! }.to_not raise_error
57
+ end
58
+
59
+ context "when manifest contains a node group that is not part of the plugin" do
60
+ let(:attributes) {
61
+ {
62
+ nodes: [
63
+ {
64
+ groups: ["not::defined"],
65
+ hosts: ["one.riotgames.com"]
66
+ }
67
+ ]
68
+ }
69
+ }
70
+
71
+ it "raises an InvalidBootstrapManifest error" do
72
+ expect {
73
+ validate!
74
+ }.to raise_error(
75
+ MB::InvalidBootstrapManifest,
76
+ "Manifest describes the node group 'not::defined' which is not found in the given routine for 'pvpnet (1.2.3)'"
77
+ )
78
+ end
79
+ end
80
+
81
+ context "when a key is not in proper node group format: '{component}::{group}'" do
82
+ let(:attributes) {
83
+ {
84
+ nodes: [
85
+ {
86
+ groups: ["activemq"],
87
+ hosts: ["amq1.riotgames.com"]
88
+ },
89
+
90
+ {
91
+ groups: ["nginx::master"],
92
+ hosts: ["nginx1.riotgames.com"]
93
+ },
94
+ ]
95
+ }
96
+ }
97
+
98
+ it "raises an InvalidBootstrapManifest error" do
99
+ expect {
100
+ validate!
101
+ }.to raise_error(
102
+ MB::InvalidBootstrapManifest,
103
+ "Manifest contained the entry: 'activemq' which is not in the proper node group format: 'component::group'"
104
+ )
105
+ end
106
+ end
107
+
108
+ context "when there is no nodes key" do
109
+ let(:attributes) { { "component::group" => ["box1"] } }
110
+
111
+ it "raises an InvalidBootstrapManifest error" do
112
+ expect {
113
+ validate!
114
+ }.to raise_error(
115
+ MB::InvalidBootstrapManifest
116
+ )
117
+ end
118
+ end
119
+ end
120
+
121
+ describe "::from_provisioner" do
122
+ let(:manifest) {
123
+ described_class.from_provisioner(provisioner_response, provisioner_manifest, path)
124
+ }
125
+
126
+ let(:provisioner_response) {
127
+ [
128
+ {
129
+ instance_type: "m1.large",
130
+ public_hostname: "cloud-1.riotgames.com"
131
+ },
132
+ {
133
+ instance_type: "m1.small",
134
+ public_hostname: "cloud-2.riotgames.com"
135
+ },
136
+ {
137
+ instance_type: "m1.small",
138
+ public_hostname: "cloud-3.riotgames.com"
139
+ }
140
+ ]
141
+ }
142
+
143
+ let(:provisioner_manifest) {
144
+ MB::Provisioner::Manifest.new(
145
+ {
146
+ options: {
147
+ ssh: {
148
+ user: "root",
149
+ password: "secret"
150
+ }
151
+ },
152
+ nodes: [
153
+ {
154
+ type: "m1.large",
155
+ count: 1,
156
+ groups: ["activemq::master"]
157
+ },
158
+ {
159
+ type: "m1.small",
160
+ count: 2,
161
+ groups: ["activemq::slave"]
162
+ }
163
+ ]
164
+ }
165
+ )
166
+ }
167
+
168
+ let(:path) { nil }
169
+
170
+ it { should be_a(MB::Bootstrap::Manifest) }
171
+
172
+ it "has a 'nodes' key" do
173
+ subject.should have_key(:nodes)
174
+ end
175
+
176
+ it "has a node group for each node group in the provisioner manifest" do
177
+ subject.node_groups.should have(provisioner_manifest[:nodes].length).items
178
+ end
179
+
180
+ it "each node group contains two keys: 'groups' and 'hosts'" do
181
+ subject.node_groups.each do |node_group|
182
+ node_group.keys.should have(2).items
183
+ node_group.should have_key(:groups)
184
+ node_group.should have_key(:hosts)
185
+ end
186
+ end
187
+
188
+ it "has an appropriate number of hosts for the desired count" do
189
+ subject[:nodes].should =~ [
190
+ {
191
+ groups: ["activemq::master"],
192
+ hosts: [
193
+ "cloud-1.riotgames.com"
194
+ ]
195
+ },
196
+ {
197
+ groups: ["activemq::slave"],
198
+ hosts: [
199
+ "cloud-2.riotgames.com",
200
+ "cloud-3.riotgames.com"
201
+ ]
202
+ }
203
+ ]
204
+ end
205
+
206
+ it "copies the options key" do
207
+ subject[:options].should == {
208
+ ssh: {
209
+ user: "root",
210
+ password: "secret"
211
+ }
212
+ }
213
+ end
214
+
215
+ context "multiple nodes of the same type returned by the provisioner" do
216
+ let(:provisioner_response) do
217
+ [
218
+ {
219
+ instance_type: "m1.large",
220
+ public_hostname: "cloud-1.riotgames.com"
221
+ },
222
+ {
223
+ instance_type: "m1.large",
224
+ public_hostname: "cloud-2.riotgames.com"
225
+ },
226
+ {
227
+ instance_type: "m1.large",
228
+ public_hostname: "cloud-3.riotgames.com"
229
+ }
230
+ ]
231
+ end
232
+
233
+ let(:provisioner_manifest) do
234
+ MB::Provisioner::Manifest.new(
235
+ nodes: [
236
+ {
237
+ type: "m1.large",
238
+ count: 1,
239
+ groups: ["activemq::master"]
240
+ },
241
+ {
242
+ type: "m1.large",
243
+ count: 2,
244
+ groups: ["activemq::slave"]
245
+ }
246
+ ]
247
+ )
248
+ end
249
+
250
+ subject do
251
+ described_class.from_provisioner(provisioner_response, provisioner_manifest)
252
+ end
253
+
254
+ it "has an appropriate number of hosts for the desired count" do
255
+ subject[:nodes].should =~ [
256
+ {
257
+ groups: ["activemq::master"],
258
+ hosts: [
259
+ "cloud-1.riotgames.com"
260
+ ]
261
+ },
262
+ {
263
+ groups: ["activemq::slave"],
264
+ hosts: [
265
+ "cloud-2.riotgames.com",
266
+ "cloud-3.riotgames.com"
267
+ ]
268
+ }
269
+ ]
270
+ end
271
+ end
272
+
273
+ context "given a value for the path argument" do
274
+ let(:path) { '/tmp/path' }
275
+
276
+ it "sets the path value" do
277
+ manifest.path.should eql('/tmp/path')
278
+ end
279
+ end
280
+
281
+ context "given one node returned by the provisioner and a manifest containing with multiple groups" do
282
+ let(:response) do
283
+ [
284
+ {
285
+ instance_type: "m1.large",
286
+ public_hostname: "cloud-1.riotgames.com"
287
+ }
288
+ ]
289
+ end
290
+
291
+ let(:provisioner_manifest) do
292
+ MB::Provisioner::Manifest.new(
293
+ {
294
+ nodes: [
295
+ {
296
+ type: "m1.large",
297
+ groups: ["activemq::master", "activemq::slave"],
298
+ count: 1
299
+ }
300
+ ]
301
+ }
302
+ )
303
+ end
304
+
305
+ subject do
306
+ described_class.from_provisioner(response, provisioner_manifest)
307
+ end
308
+
309
+ it "contains one node group" do
310
+ subject.node_groups.should have(1).item
311
+ end
312
+
313
+ it "has one host in that node group" do
314
+ subject.node_groups.first[:hosts].should have(1).item
315
+ end
316
+
317
+ it "has two groups in that node group" do
318
+ subject.node_groups.first[:groups].should have(2).items
319
+ end
320
+
321
+ it "has an appropriate number of hosts for the desired count" do
322
+ subject.node_groups.should =~ [
323
+ {
324
+ groups: ["activemq::master", "activemq::slave"],
325
+ hosts: [
326
+ "cloud-1.riotgames.com"
327
+ ]
328
+ }
329
+ ]
330
+ end
331
+ end
332
+ end
333
+ end