motherbrain 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/mb/application.rb +4 -0
- data/lib/mb/cli/sub_command/plugin.rb +1 -2
- data/lib/mb/environment_manager.rb +1 -1
- data/lib/mb/gears/dynamic_service.rb +34 -31
- data/lib/mb/node_querier.rb +6 -6
- data/lib/mb/plugin_manager.rb +40 -0
- data/lib/mb/version.rb +1 -1
- data/spec/unit/mb/gears/dynamic_service_spec.rb +45 -28
- data/spec/unit/mb/node_querier_spec.rb +28 -0
- data/spec/unit/mb/plugin_manager_spec.rb +17 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0fa9b41a671b085f2e3c08057bd1f2e3db7a61f2
|
4
|
+
data.tar.gz: eadec4b2bc486a6b52eedf09d9c630608c4927db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52cadf2941cdd996c6f5f869cdafc0758a1d20f910e28d5e66fb0d1e23410b9d72d07b045b3ba3656edc737605470a675b9e0f2028f8e3235a2e8120377e6e62
|
7
|
+
data.tar.gz: 035faec4ac2787b08b3491d49506caa3ac06a740c7ac37a1a8b8eb72122021051ff1c143c786a9a8863ba4ac44987aaef80b09346c87ace569933922559a9032
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 1.2.1
|
2
|
+
|
3
|
+
* [#705](https://github.com/RiotGames/motherbrain/pull/705) Make connector_for_os public and ensure it is used for environment upgrades
|
4
|
+
* [#704](https://github.com/RiotGames/motherbrain/pull/704) Ensure the component and service exist before attempting to execute the service state change
|
5
|
+
* [#702](https://github.com/RiotGames/motherbrain/pull/702) Add a global error handler to help log unexpected, uncaught exceptions
|
6
|
+
|
1
7
|
# 1.2.0
|
2
8
|
|
3
9
|
* [#701](https://github.com/RiotGames/motherbrain/pull/701) Pass OS hints through to ridley-connectors for smarter connector picking
|
data/lib/mb/application.rb
CHANGED
@@ -63,6 +63,10 @@ module MotherBrain
|
|
63
63
|
# @param [MB::Config] config
|
64
64
|
def run!(config)
|
65
65
|
Celluloid.boot
|
66
|
+
Celluloid.exception_handler do |ex|
|
67
|
+
log.fatal { "Application received unhandled exception: #{ex.class} - #{ex.message}" }
|
68
|
+
log.fatal { ex.backtrace.join("\n\t") }
|
69
|
+
end
|
66
70
|
log.info { "motherbrain starting..." }
|
67
71
|
setup
|
68
72
|
@instance = Application::SupervisionGroup.new(config)
|
@@ -164,7 +164,7 @@ module MotherBrain
|
|
164
164
|
service_options = Hash.new.merge(options).deep_symbolize_keys
|
165
165
|
service_options[:node_filter] = service_options.delete(:only)
|
166
166
|
|
167
|
-
job =
|
167
|
+
job = plugin_manager.async_change_service_state(
|
168
168
|
service.freeze,
|
169
169
|
plugin.freeze,
|
170
170
|
environment.freeze,
|
@@ -172,7 +172,6 @@ module MotherBrain
|
|
172
172
|
true,
|
173
173
|
service_options
|
174
174
|
)
|
175
|
-
|
176
175
|
CliClient.new(job).display
|
177
176
|
end
|
178
177
|
|
@@ -81,7 +81,7 @@ module MotherBrain
|
|
81
81
|
|
82
82
|
job.set_status("Performing a chef client run on #{nodes.length} nodes")
|
83
83
|
nodes.collect do |node|
|
84
|
-
node_querier.future(:chef_run, node.public_hostname, connector: node.chef_attributes.os)
|
84
|
+
node_querier.future(:chef_run, node.public_hostname, connector: node_querier.connector_for_os(node.chef_attributes.os))
|
85
85
|
end.each do |future|
|
86
86
|
begin
|
87
87
|
response = future.value
|
@@ -3,34 +3,6 @@ module MotherBrain
|
|
3
3
|
class DynamicService < Gear::Base
|
4
4
|
class << self
|
5
5
|
|
6
|
-
# Parses a service, creates a new instance of DynamicService
|
7
|
-
# and executes a Chef run to change the state of the service.
|
8
|
-
#
|
9
|
-
# @param service [String]
|
10
|
-
# a dotted string "component.service_name"
|
11
|
-
# @param plugin [MB::Plugin]
|
12
|
-
# the plugin currently in use
|
13
|
-
# @param environment [String]
|
14
|
-
# the environment to operate on
|
15
|
-
# @param state [String]
|
16
|
-
# the state of the service to change to
|
17
|
-
# @param options [Hash]
|
18
|
-
#
|
19
|
-
# @return [MB::JobTicket]
|
20
|
-
def change_service_state(service, plugin, environment, state, run_chef = true, options = {})
|
21
|
-
job = Job.new(:dynamic_service_state_change)
|
22
|
-
|
23
|
-
component, service_name = service.split('.')
|
24
|
-
raise InvalidDynamicService.new(component, service_name) unless component && service_name
|
25
|
-
dynamic_service = new(component, service_name)
|
26
|
-
dynamic_service.state_change(job, plugin, environment, state, run_chef, options)
|
27
|
-
job.report_success
|
28
|
-
job.ticket
|
29
|
-
rescue => ex
|
30
|
-
job.report_failure(ex)
|
31
|
-
ensure
|
32
|
-
job.terminate if job && job.alive?
|
33
|
-
end
|
34
6
|
end
|
35
7
|
|
36
8
|
include MB::Mixin::Services
|
@@ -72,19 +44,24 @@ module MotherBrain
|
|
72
44
|
#
|
73
45
|
# @return [MB::JobTicket]
|
74
46
|
def state_change(job, plugin, environment, state, run_chef = true, options = {})
|
47
|
+
job.report_running
|
48
|
+
job.set_status("Transitioning #{@component}.#{@name} to #{state}...")
|
75
49
|
log.warn {
|
76
50
|
"Component's service state is being changed to #{state}, which is not one of #{COMMON_STATES}"
|
77
51
|
} unless COMMON_STATES.include?(state)
|
78
52
|
|
79
53
|
chef_synchronize(chef_environment: environment, force: options[:force]) do
|
80
|
-
|
54
|
+
unless valid_dynamic_service?(plugin)
|
55
|
+
job.report_failure("#{@component}.#{@name} is not a valid service in #{plugin.name}")
|
56
|
+
return job
|
57
|
+
end
|
58
|
+
|
59
|
+
component_object = plugin.component(component)
|
81
60
|
service_object = component_object.get_service(name)
|
82
61
|
group = component_object.group(service_object.service_group)
|
83
62
|
nodes = group.nodes(environment)
|
84
63
|
nodes = MB::NodeFilter.filter(options[:node_filter], nodes) if options[:node_filter]
|
85
64
|
|
86
|
-
job.report_running("preparing to change the #{name} service to #{state}")
|
87
|
-
|
88
65
|
if options[:cluster_override]
|
89
66
|
set_environment_attribute(job, environment, service_object.service_attribute, state)
|
90
67
|
else
|
@@ -95,6 +72,13 @@ module MotherBrain
|
|
95
72
|
node_querier.bulk_chef_run(job, nodes, service_object.service_recipe)
|
96
73
|
end
|
97
74
|
end
|
75
|
+
job.set_status("Finished transitioning #{@component}.#{@name} to #{state}!")
|
76
|
+
job.report_success
|
77
|
+
job.ticket
|
78
|
+
rescue => ex
|
79
|
+
job.report_failure(ex)
|
80
|
+
ensure
|
81
|
+
job.terminate if job && job.alive?
|
98
82
|
end
|
99
83
|
|
100
84
|
def node_state_change(job, plugin, node, state, run_chef = true)
|
@@ -216,6 +200,25 @@ module MotherBrain
|
|
216
200
|
end
|
217
201
|
end
|
218
202
|
|
203
|
+
private
|
204
|
+
|
205
|
+
# Returns whether the component name and service name comprises a valid service contained in the plugin
|
206
|
+
#
|
207
|
+
# @param [MB::Plugin] plugin
|
208
|
+
# the plugin to check for components
|
209
|
+
#
|
210
|
+
# @return [Boolean]
|
211
|
+
def valid_dynamic_service?(plugin)
|
212
|
+
return false if plugin.nil? or @component.nil? or @name.nil?
|
213
|
+
|
214
|
+
component = plugin.component(@component)
|
215
|
+
return false if component.nil?
|
216
|
+
|
217
|
+
service = component.get_service(@name)
|
218
|
+
return false if service.nil?
|
219
|
+
|
220
|
+
true
|
221
|
+
end
|
219
222
|
end
|
220
223
|
end
|
221
224
|
end
|
data/lib/mb/node_querier.rb
CHANGED
@@ -477,12 +477,6 @@ module MotherBrain
|
|
477
477
|
job.terminate if job && job.alive?
|
478
478
|
end
|
479
479
|
|
480
|
-
private
|
481
|
-
|
482
|
-
def finalize_callback
|
483
|
-
log.debug { "Node Querier stopping..." }
|
484
|
-
end
|
485
|
-
|
486
480
|
# Returns a String representing the best connector
|
487
481
|
# type to use when communicating with a given node
|
488
482
|
#
|
@@ -501,6 +495,12 @@ module MotherBrain
|
|
501
495
|
end
|
502
496
|
end
|
503
497
|
|
498
|
+
private
|
499
|
+
|
500
|
+
def finalize_callback
|
501
|
+
log.debug { "Node Querier stopping..." }
|
502
|
+
end
|
503
|
+
|
504
504
|
# Run a Ruby script on the target host and return the result of STDOUT. Only scripts
|
505
505
|
# that are located in the Mother Brain scripts directory can be used and they should
|
506
506
|
# be identified just by their filename minus the extension
|
data/lib/mb/plugin_manager.rb
CHANGED
@@ -509,6 +509,46 @@ module MotherBrain
|
|
509
509
|
[]
|
510
510
|
end
|
511
511
|
|
512
|
+
# Runs #change_service_state asynchronously
|
513
|
+
#
|
514
|
+
# @param service [String]
|
515
|
+
# a dotted string "component.service_name"
|
516
|
+
# @param plugin [MB::Plugin]
|
517
|
+
# the plugin currently in use
|
518
|
+
# @param environment [String]
|
519
|
+
# the environment to operate on
|
520
|
+
# @param state [String]
|
521
|
+
# the state of the service to change to
|
522
|
+
# @param options [Hash]
|
523
|
+
#
|
524
|
+
# @return [MB::JobTicket]
|
525
|
+
def async_change_service_state(service, plugin, environment, state, run_chef = true, options = {})
|
526
|
+
job = Job.new(:dynamic_service_state_change)
|
527
|
+
async(:change_service_state, job, service, plugin, environment, state, run_chef, options)
|
528
|
+
job.ticket
|
529
|
+
end
|
530
|
+
|
531
|
+
# Parses a service, creates a new instance of DynamicService
|
532
|
+
# and executes a Chef run to change the state of the service.
|
533
|
+
#
|
534
|
+
# @param job [MB::Job]
|
535
|
+
# the job to report status on
|
536
|
+
# @param service [String]
|
537
|
+
# a dotted string "component.service_name"
|
538
|
+
# @param plugin [MB::Plugin]
|
539
|
+
# the plugin currently in use
|
540
|
+
# @param environment [String]
|
541
|
+
# the environment to operate on
|
542
|
+
# @param state [String]
|
543
|
+
# the state of the service to change to
|
544
|
+
# @param options [Hash]
|
545
|
+
def change_service_state(job, service, plugin, environment, state, run_chef = true, options = {})
|
546
|
+
component_name, service_name = service.split('.')
|
547
|
+
dynamic_service = MB::Gear::DynamicService.new(component_name, service_name)
|
548
|
+
dynamic_service.state_change(job, plugin, environment, state, run_chef, options)
|
549
|
+
end
|
550
|
+
|
551
|
+
|
512
552
|
protected
|
513
553
|
|
514
554
|
def reconfigure(_msg, config)
|
data/lib/mb/version.rb
CHANGED
@@ -11,34 +11,6 @@ describe MB::Gear::DynamicService do
|
|
11
11
|
let(:nodes) { [ node1, node2 ] }
|
12
12
|
let(:node1) { double(name: nil, reload: nil, set_chef_attribute: nil, save: nil) }
|
13
13
|
let(:node2) { double(name: nil, reload: nil, set_chef_attribute: nil, save: nil) }
|
14
|
-
let(:job) { MB::Job.new(:thejob) }
|
15
|
-
describe "ClassMethods" do
|
16
|
-
let(:service) { "webapp.tomcat" }
|
17
|
-
|
18
|
-
before do
|
19
|
-
dynamic_service.stub(:state_change)
|
20
|
-
MB::Job.should_receive(:new).and_return(job)
|
21
|
-
end
|
22
|
-
|
23
|
-
describe "::change_service_state" do
|
24
|
-
let(:change_service_state) { MB::Gear::DynamicService.change_service_state(service, plugin, environment, state) }
|
25
|
-
|
26
|
-
it "splits the service on a period" do
|
27
|
-
expect(MB::Gear::DynamicService).to receive(:new).with('webapp', 'tomcat').and_return(dynamic_service)
|
28
|
-
change_service_state
|
29
|
-
end
|
30
|
-
|
31
|
-
context "when the service is not formatted as 'COMPONENT.SERVICE'" do
|
32
|
-
let(:service) { "foo" }
|
33
|
-
|
34
|
-
it "raises an error" do
|
35
|
-
job.should_receive(:report_failure).with(kind_of(MB::InvalidDynamicService))
|
36
|
-
change_service_state
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
14
|
let(:job) { double(alive?: false, report_running: nil, set_status: nil, report_success: nil, ticket: nil) }
|
43
15
|
|
44
16
|
describe "#state_change" do
|
@@ -184,4 +156,49 @@ describe MB::Gear::DynamicService do
|
|
184
156
|
set_node_attribute
|
185
157
|
end
|
186
158
|
end
|
159
|
+
|
160
|
+
describe "#valid_dynamic_service?" do
|
161
|
+
let(:plugin) { double(MB::Plugin) }
|
162
|
+
let(:component_name) { "component_name" }
|
163
|
+
let(:component) { double(MB::Component, name: component_name) }
|
164
|
+
let(:service_name) { "service_name" }
|
165
|
+
let(:service) { double(MB::Gear::DynamicService, name: service_name) }
|
166
|
+
|
167
|
+
let(:check) { subject.send(:valid_dynamic_service?, plugin) }
|
168
|
+
|
169
|
+
subject { MB::Gear::DynamicService.new(component_name, service_name) }
|
170
|
+
|
171
|
+
it "should return false when the plugin is nil" do
|
172
|
+
expect(subject.send(:valid_dynamic_service?, nil)).to be_false
|
173
|
+
end
|
174
|
+
|
175
|
+
context "should return false when the component_name is nil" do
|
176
|
+
let(:component_name) { nil }
|
177
|
+
|
178
|
+
it { expect(subject.send(:valid_dynamic_service?, plugin)).to be_false }
|
179
|
+
end
|
180
|
+
|
181
|
+
context "should return false when the service_name is nil" do
|
182
|
+
let(:service_name) { nil }
|
183
|
+
|
184
|
+
it { expect(subject.send(:valid_dynamic_service?, plugin)).to be_false }
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should return false when the component cannot be found in the plugin" do
|
188
|
+
plugin.stub(:component).with(component_name).and_return nil
|
189
|
+
expect(check).to be_false
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should return false when the service cannot be found in the plugin" do
|
193
|
+
plugin.stub(:component).with(component_name).and_return component
|
194
|
+
component.stub(:get_service).with(service_name).and_return nil
|
195
|
+
expect(check).to be_false
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should return true when the service can be found in the component" do
|
199
|
+
plugin.stub(:component).with(component_name).and_return component
|
200
|
+
component.stub(:get_service).with(service_name).and_return service
|
201
|
+
expect(check).to be_true
|
202
|
+
end
|
203
|
+
end
|
187
204
|
end
|
@@ -529,4 +529,32 @@ describe MB::NodeQuerier do
|
|
529
529
|
end
|
530
530
|
end
|
531
531
|
end
|
532
|
+
|
533
|
+
describe "#connector_for_os" do
|
534
|
+
let(:connector_for_os) { subject.connector_for_os(os) }
|
535
|
+
|
536
|
+
context "when the os is windows" do
|
537
|
+
let(:os) { "windows" }
|
538
|
+
|
539
|
+
it "returns winrm" do
|
540
|
+
expect(connector_for_os).to eql("winrm")
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
context "when the os is linux" do
|
545
|
+
let(:os) { "linux" }
|
546
|
+
|
547
|
+
it "returns ssh" do
|
548
|
+
expect(connector_for_os).to eql("ssh")
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
context "when the os is not windows or linux" do
|
553
|
+
let(:os) { "solaris" }
|
554
|
+
|
555
|
+
it "returns nil" do
|
556
|
+
expect(connector_for_os).to be_nil
|
557
|
+
end
|
558
|
+
end
|
559
|
+
end
|
532
560
|
end
|
@@ -721,4 +721,21 @@ describe MotherBrain::PluginManager do
|
|
721
721
|
end
|
722
722
|
end
|
723
723
|
end
|
724
|
+
|
725
|
+
describe "#change_service_state" do
|
726
|
+
let(:component_name) { "component_name" }
|
727
|
+
let(:service_name) { "service_name" }
|
728
|
+
let(:job) { double(MB::Job) }
|
729
|
+
let(:plugin) { double(MB::Plugin) }
|
730
|
+
let(:environment) { "environment" }
|
731
|
+
|
732
|
+
it "should split the service compound name into the component and service names" do
|
733
|
+
dynamic_service = double(MB::Gear::DynamicService)
|
734
|
+
dynamic_service.stub(:state_change)
|
735
|
+
|
736
|
+
MB::Gear::DynamicService.should_receive(:new).with(component_name, service_name).and_return dynamic_service
|
737
|
+
|
738
|
+
subject.change_service_state(job, "#{component_name}.#{service_name}", plugin, environment, "start")
|
739
|
+
end
|
740
|
+
end
|
724
741
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motherbrain
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jamie Winsor
|
@@ -15,7 +15,7 @@ authors:
|
|
15
15
|
autorequire:
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
|
-
date: 2014-
|
18
|
+
date: 2014-05-01 00:00:00.000000000 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: celluloid
|