motherbrain 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|