motherbrain 1.3.0 → 1.4.0

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: acc003650c5db20ee4a4198443bfc04339a538e9
4
- data.tar.gz: ec7dbd88bac26101aa0029882106b775352025f2
3
+ metadata.gz: dfdaf1022206cdd6053520eed112b1fedc5c25a8
4
+ data.tar.gz: 5b86141638b4849c04c864759e9a0546cca56ff4
5
5
  SHA512:
6
- metadata.gz: c55553d715c7e67ac0a8627f3bffec8ce9ae47577c1601aa32242f75dade46acbb1d8614840df5ce8fddba26a028c91d91921fc88e0856aa5d98336e4811a0d6
7
- data.tar.gz: 3f7d389d0ddcf5e067c417ada1412ff825710a3219bf83283808ff62bdf48ccf130ff0a5d8d6d97470b3a53e9c03eb1f0d8f9fffe17f8b606ce2bcc5ec233389
6
+ metadata.gz: d3be6473cedd6894ac603158363ddfb0249fa9bea09c747d8ef1957cdb5a82cd0976f56cb5a6857a692929656a2d51aa3128438ff766e1116d61d00b522debda
7
+ data.tar.gz: 2017c5767c5f3aa705fd9dd991b65b76f4f7b5dd113192702a7eb03911d3c1b8a5a7ca6fd47bac11c3f6a5c2d6157d6c098a376d233f5d87390ef41ff1a1e794
@@ -7,18 +7,21 @@ module MotherBrain::API
7
7
  require_relative 'v1/jobs_endpoint'
8
8
  require_relative 'v1/plugins_endpoint'
9
9
  require_relative 'v1/chef_endpoint'
10
+ require_relative 'v1/server_control_endpoint'
10
11
 
11
12
  version 'v1', using: :header, vendor: 'motherbrain'
12
13
  format :json
13
14
  default_format :json
14
15
 
15
- rescue_from Grape::Exceptions::Validation do |e|
16
+ JSON_CONTENT_TYPE = {"Content-type" => "application/json"}
17
+
18
+ rescue_from Grape::Exceptions::Validation do |ex|
16
19
  body = MultiJson.encode(
17
- status: e.status,
18
- message: e.message,
19
- param: e.param
20
+ status: ex.status,
21
+ message: ex.message,
22
+ param: ex.param
20
23
  )
21
- rack_response(body, e.status, "Content-type" => "application/json")
24
+ rack_response(body, ex.status, JSON_CONTENT_TYPE)
22
25
  end
23
26
 
24
27
  rescue_from :all do |ex|
@@ -29,13 +32,31 @@ module MotherBrain::API
29
32
  MultiJson.encode(code: -1, message: ex.message)
30
33
  end
31
34
 
32
- rack_response(body, 500, "Content-type" => "application/json")
35
+ http_status_code = if ex.is_a?(MB::APIError)
36
+ ex.http_status_code
37
+ else
38
+ 500
39
+ end
40
+
41
+ rack_response(body, http_status_code, JSON_CONTENT_TYPE)
33
42
  end
34
43
 
35
44
  before do
36
45
  header['Access-Control-Allow-Origin'] = '*'
37
46
  header['Access-Control-Request-Method'] = '*'
38
47
  header.delete('Transfer-Encoding')
48
+
49
+ server_control_endpoint = false
50
+ MB::API::V1::ServerControlEndpoint.endpoints.each do |endpoint|
51
+ endpoint.routes.each do |route|
52
+ if request.path =~ route.route_compiled
53
+ server_control_endpoint = true
54
+ break
55
+ end
56
+ end
57
+ end
58
+
59
+ raise MB::ApplicationPaused.new if MB::Application.paused? && !server_control_endpoint
39
60
  end
40
61
 
41
62
  mount V1::ConfigEndpoint
@@ -43,6 +64,7 @@ module MotherBrain::API
43
64
  mount V1::EnvironmentsEndpoint
44
65
  mount V1::PluginsEndpoint
45
66
  mount V1::ChefEndpoint
67
+ mount V1::ServerControlEndpoint
46
68
  add_swagger_documentation
47
69
 
48
70
  if MB.testing?
@@ -0,0 +1,37 @@
1
+ module MotherBrain::API
2
+ class V1
3
+ class ServerControlEndpoint < MB::API::Endpoint
4
+ helpers MB::Mixin::Services
5
+
6
+ desc "resume the server, preventing new requests from being processed"
7
+ put 'resume' do
8
+ MB::Application.resume
9
+ server_status
10
+ end
11
+
12
+ desc "pause the server, preventing new requests from being processed"
13
+ put 'pause' do
14
+ MB::Application.pause
15
+ server_status
16
+ end
17
+
18
+ desc "stop the server"
19
+ put 'stop' do
20
+ MB::Application.stop
21
+ status(202)
22
+ server_status
23
+ end
24
+
25
+ desc "get the server status"
26
+ get 'status' do
27
+ server_status
28
+ end
29
+
30
+ helpers do
31
+ def server_status
32
+ {server_status: MB::Application.status}
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -28,6 +28,12 @@ module MotherBrain
28
28
  # @example running the application in the background
29
29
  # MB::Application.run!(config)
30
30
  module Application
31
+ module Status
32
+ RUNNING = :running
33
+ PAUSED = :paused
34
+ STOPPING = :stopping
35
+ end
36
+
31
37
  class << self
32
38
  extend Forwardable
33
39
  include MB::Mixin::Services
@@ -95,7 +101,29 @@ module MotherBrain
95
101
 
96
102
  # Stop the running application
97
103
  def stop
98
- instance.terminate
104
+ instance.async_interrupt(3)
105
+ @status = Status::STOPPING
106
+ end
107
+
108
+ # Set the application state to paused. This allows actors to
109
+ # continue processing, but causes the RestGateway not to accept
110
+ # new requests.
111
+ #
112
+ # See: MotherBrain::API::V1 L51, 'before' block
113
+ def pause
114
+ @status = Status::PAUSED
115
+ end
116
+
117
+ def resume
118
+ @status = Status::RUNNING
119
+ end
120
+
121
+ def paused?
122
+ status == Status::PAUSED
123
+ end
124
+
125
+ def status
126
+ @status ||= Status::RUNNING
99
127
  end
100
128
  end
101
129
 
@@ -135,7 +163,12 @@ module MotherBrain
135
163
  @registry[:ridley].async.configure(new_config.to_ridley)
136
164
  end
137
165
 
138
- def interrupt
166
+ def async_interrupt(delay = 0)
167
+ future.interrupt(delay)
168
+ end
169
+
170
+ def interrupt(delay = 0)
171
+ Celluloid.sleep(delay) if delay > 0
139
172
  interrupt_mutex.synchronize do
140
173
  unless interrupted
141
174
  @interrupted = true
@@ -63,6 +63,8 @@ module MotherBrain
63
63
 
64
64
  def initialize(berksfile_lock_path)
65
65
  @berksfile_lock = ::Berkshelf::Lockfile.from_file(berksfile_lock_path)
66
+ rescue ::Berkshelf::LockfileParserError
67
+ log.warn "Unable to parse Berksfile.lock - maybe it's an old format?"
66
68
  end
67
69
 
68
70
  # Return a hash of all of the cookbook versions found in the Berksfile.lock
@@ -89,6 +89,23 @@ module MotherBrain
89
89
  end
90
90
  end
91
91
 
92
+ class APIError < MBError
93
+ DEFAULT_HTTP_STATUS_CODE = 500
94
+
95
+ class << self
96
+ # @param [Integer] code
97
+ #
98
+ # @return [Integer]
99
+ def http_status_code(code = DEFAULT_HTTP_STATUS_CODE)
100
+ @exit_code ||= code
101
+ end
102
+ end
103
+
104
+ def http_status_code
105
+ self.class.http_status_code
106
+ end
107
+ end
108
+
92
109
  # Internal errors
93
110
  class InternalError < MBError
94
111
  exit_code(99)
@@ -695,4 +712,13 @@ module MotherBrain
695
712
  exit_code(25)
696
713
  error_code(3029)
697
714
  end
715
+
716
+ class ApplicationPaused < APIError
717
+ error_code(3330)
718
+ http_status_code(503)
719
+
720
+ def message
721
+ "MotherBrain is paused. It will not accept new requests until it is resumed."
722
+ end
723
+ end
698
724
  end
@@ -322,6 +322,7 @@ module MotherBrain
322
322
  scratch_dir = FileSystem.tmpdir("cbplugin")
323
323
  metadata_path = File.join(scratch_dir, CookbookMetadata::JSON_FILENAME)
324
324
  plugin_path = File.join(scratch_dir, Plugin::PLUGIN_FILENAME)
325
+ lockfile_path = File.join(scratch_dir, Berkshelf::Lockfile::BERKSFILE_LOCK)
325
326
 
326
327
  File.write(metadata_path, resource.metadata.to_json)
327
328
 
@@ -329,6 +330,10 @@ module MotherBrain
329
330
  raise PluginLoadError, "failure downloading plugin file for #{resource.name}"
330
331
  end
331
332
 
333
+ unless resource.download_file(:root_file, Berkshelf::Lockfile::BERKSFILE_LOCK, lockfile_path)
334
+ log.info "No Berksfile.lock found for #{resource.name} - won't be able to use cookbook versions from lockfile"
335
+ end
336
+
332
337
  load_file(scratch_dir, options)
333
338
  rescue PluginSyntaxError, PluginLoadError => ex
334
339
  err_msg = "could not load remote plugin #{name} (#{version}): #{ex.message}"
@@ -29,7 +29,7 @@ module MotherBrain
29
29
 
30
30
  include MB::Logging
31
31
 
32
- DEFAULT_PORT = ENV["PORT"].to_i || 26100
32
+ DEFAULT_PORT = ENV["PORT"] ? ENV["PORT"].to_i : 26100
33
33
 
34
34
  DEFAULT_OPTIONS = {
35
35
  host: '0.0.0.0',
@@ -59,7 +59,12 @@ module MotherBrain
59
59
  options[:Port] = options[:port]
60
60
 
61
61
  log.info { "REST Gateway listening on #{options[:host]}:#{options[:port]}" }
62
- super(app, options)
62
+
63
+ begin
64
+ super(app, options)
65
+ rescue Errno::EADDRINUSE
66
+ log.fatal { "Port #{options[:port]} is already in use. Unable to start rest gateway." }
67
+ end
63
68
  end
64
69
 
65
70
  private
@@ -1,3 +1,3 @@
1
1
  module MotherBrain
2
- VERSION = '1.3.0'
2
+ VERSION = '1.4.0'
3
3
  end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe MB::API::V1::ServerControlEndpoint do
4
+ include Rack::Test::Methods
5
+
6
+ before(:all) { MB::RestGateway.start(port: 26101) }
7
+ after(:all) { MB::RestGateway.stop }
8
+ after(:each) { MB::Application.resume }
9
+ let(:app) { MB::RestGateway.instance.app }
10
+
11
+ describe "PUT /pause" do
12
+ it "pauses the server" do
13
+ put '/pause'
14
+ last_response.status.should == 200
15
+ JSON.parse(last_response.body).should eq("server_status" => "paused")
16
+ end
17
+
18
+ it "prevents actions while the server is paused" do
19
+ put '/pause'
20
+ json_post "/environments/environmentname/upgrade",
21
+ MultiJson.dump(plugin: { name: 'pluginname', version: '1.0.0' })
22
+ last_response.status.should == 503
23
+ JSON.parse(last_response.body).should eq("code"=>3330, "message"=>"MotherBrain is paused. It will not accept new requests until it is resumed.")
24
+ end
25
+ end
26
+
27
+ describe "PUT /resume" do
28
+ before do
29
+ MB::Application.pause
30
+ end
31
+
32
+ it "resumes the server" do
33
+ put '/resume'
34
+ last_response.status.should == 200
35
+ JSON.parse(last_response.body).should eq("server_status" => "running")
36
+ end
37
+
38
+ it "allows actions while the server is resumed" do
39
+ put '/resume'
40
+ json_post "/environments/environmentname/upgrade",
41
+ MultiJson.dump(plugin: { name: 'pluginname', version: '1.0.0' })
42
+ last_response.status.should == 201
43
+ end
44
+ end
45
+
46
+ describe "PUT /stop" do
47
+ it "stops the server" do
48
+ MB::Application.instance.should_receive(:async_interrupt).with(3)
49
+ put '/stop'
50
+ last_response.status.should == 202
51
+ JSON.parse(last_response.body).should eq("server_status" => "stopping")
52
+ end
53
+ end
54
+ end
@@ -15,5 +15,19 @@ describe MB::Application do
15
15
  subject.config.should be_a(MB::Config)
16
16
  end
17
17
  end
18
+
19
+ describe "::pause" do
20
+ it "should pause" do
21
+ subject.pause
22
+ expect(subject.paused?).to eq(true)
23
+ end
24
+ end
25
+
26
+ describe "::resume" do
27
+ it "should resume" do
28
+ subject.resume
29
+ expect(subject.paused?).to eq(false)
30
+ end
31
+ end
18
32
  end
19
33
  end
@@ -10,4 +10,19 @@ describe MB::RestGateway do
10
10
  subject.app.should be_a(MB::API::Application)
11
11
  end
12
12
  end
13
+
14
+ describe "constants" do
15
+ it "should set DEFAULT_PORT to $PORT" do
16
+ p = 12345
17
+ ENV["PORT"] = p.to_s
18
+ load File.join(MB.app_root, "lib", "mb", "rest_gateway.rb")
19
+ expect(MB::RestGateway::DEFAULT_PORT).to eq(p)
20
+ end
21
+
22
+ it "should set DEFAULT_PORT to 26100 if $PORT is not set" do
23
+ ENV.delete("PORT")
24
+ load File.join(MB.app_root, "lib", "mb", "rest_gateway.rb")
25
+ expect(MB::RestGateway::DEFAULT_PORT).to eq(26100)
26
+ end
27
+ end
13
28
  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.3.0
4
+ version: 1.4.0
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-05-20 00:00:00.000000000 Z
18
+ date: 2014-05-30 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: celluloid
@@ -393,6 +393,7 @@ files:
393
393
  - lib/mb/api/v1/environments_endpoint.rb
394
394
  - lib/mb/api/v1/jobs_endpoint.rb
395
395
  - lib/mb/api/v1/plugins_endpoint.rb
396
+ - lib/mb/api/v1/server_control_endpoint.rb
396
397
  - lib/mb/api/validators.rb
397
398
  - lib/mb/api/validators/sem_ver.rb
398
399
  - lib/mb/application.rb
@@ -523,6 +524,7 @@ files:
523
524
  - spec/unit/mb/api/v1/environments_endpoint_spec.rb
524
525
  - spec/unit/mb/api/v1/jobs_endpoint_spec.rb
525
526
  - spec/unit/mb/api/v1/plugins_endpoint_spec.rb
527
+ - spec/unit/mb/api/v1/server_control_endpoint_spec.rb
526
528
  - spec/unit/mb/api/v1_spec.rb
527
529
  - spec/unit/mb/api/validators/sem_ver_spec.rb
528
530
  - spec/unit/mb/application_spec.rb
@@ -622,7 +624,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
622
624
  version: '0'
623
625
  requirements: []
624
626
  rubyforge_project:
625
- rubygems_version: 2.2.2
627
+ rubygems_version: 2.0.14
626
628
  signing_key:
627
629
  specification_version: 4
628
630
  summary: An orchestrator for Chef
@@ -673,6 +675,7 @@ test_files:
673
675
  - spec/unit/mb/api/v1/environments_endpoint_spec.rb
674
676
  - spec/unit/mb/api/v1/jobs_endpoint_spec.rb
675
677
  - spec/unit/mb/api/v1/plugins_endpoint_spec.rb
678
+ - spec/unit/mb/api/v1/server_control_endpoint_spec.rb
676
679
  - spec/unit/mb/api/v1_spec.rb
677
680
  - spec/unit/mb/api/validators/sem_ver_spec.rb
678
681
  - spec/unit/mb/application_spec.rb