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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 92e848a7145f24eb7bcaee3193cb4445382e4dc5
4
- data.tar.gz: f348b5cd5442d64ec7ecf4b575349fee037349cf
3
+ metadata.gz: 0fa9b41a671b085f2e3c08057bd1f2e3db7a61f2
4
+ data.tar.gz: eadec4b2bc486a6b52eedf09d9c630608c4927db
5
5
  SHA512:
6
- metadata.gz: 30ba05db9263dc38a8a9d57bdb3351025e69ee13e5683e97e859b6ebe8f88abaa8e7afe49495f3b7a174f2e148f66eb3a0cb315739d852c12e1a3b264e7a2e9e
7
- data.tar.gz: 8d2cf420dcfa7bede15b895f0b26f8b0a3d817a7e5473cc0449faae9c239b59f32b2c45821466e458af8ab4ccd7ed681d7ad269492c8dbede896922e01135a21
6
+ metadata.gz: 52cadf2941cdd996c6f5f869cdafc0758a1d20f910e28d5e66fb0d1e23410b9d72d07b045b3ba3656edc737605470a675b9e0f2028f8e3235a2e8120377e6e62
7
+ data.tar.gz: 035faec4ac2787b08b3491d49506caa3ac06a740c7ac37a1a8b8eb72122021051ff1c143c786a9a8863ba4ac44987aaef80b09346c87ace569933922559a9032
@@ -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
@@ -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 = MB::Gear::DynamicService.change_service_state(
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
- component_object = plugin.component(component)
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
@@ -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
@@ -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)
@@ -1,3 +1,3 @@
1
1
  module MotherBrain
2
- VERSION = '1.2.0'
2
+ VERSION = '1.2.1'
3
3
  end
@@ -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.0
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-04-29 00:00:00.000000000 Z
18
+ date: 2014-05-01 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: celluloid