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 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