motherbrain 1.3.0 → 1.4.0

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