kumo_dockercloud 3.2.0 → 3.3.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: d56a26a559069438a8dffe96f6197a43ff784a88
4
- data.tar.gz: b8b55150263ca7259d21524b8bd759c412561122
3
+ metadata.gz: 3e7a3083ac7e9265513cc75563a7b7e161e1a286
4
+ data.tar.gz: 42ae9a4b63c95fb00f8ee706cc5df18f1a81cb51
5
5
  SHA512:
6
- metadata.gz: 36456d19ed2a73171023e65d5ed7e09de4a71d4c23d1e305a4c57c73dd488c5f43963289550f88648657dd0354e0c6184d725ed55df064694927f349e30073a7
7
- data.tar.gz: 65858ebe4cabbd00d82b18e281c978cc1e2e5016925f4056a610a703844fc94b36669615bff7a8e1fe7edcc0448d9ef6945314c60a0bdbb042ac4af5ba4bbd2d
6
+ metadata.gz: 0cb94285609c0da1c6197adf3548e089000d8d8c0cdee521e38de7ca770cf5c28e2fa2b5d4b39bbcda43fb6a185297d4d2cd3df36e5c10ba499f10709637a61a
7
+ data.tar.gz: 0736d09ec8b388a88e7f415c38c94241fff02739ee0065f4a356cf34bbcba2fae25bb5031e585e9bf5e0513cc7085d6b9fef5724580872407befdc1d4048e2a6
data/Gemfile CHANGED
@@ -3,6 +3,8 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in kumo_dockercloud.gemspec
4
4
  gemspec
5
5
 
6
+ gem 'docker_cloud', git: 'git@github.com:redbubble/ruby-docker-cloud.git', branch: 'container-exec'
7
+
6
8
  group :development do
7
9
  gem 'pry'
8
10
  end
@@ -26,5 +26,6 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'rake', '~> 10.0'
27
27
  spec.add_development_dependency 'rspec', '~> 3.4'
28
28
  spec.add_development_dependency 'webmock', '~> 1.22'
29
+ spec.add_development_dependency 'pry-byebug', '~> 3.4'
29
30
  spec.add_development_dependency 'rubocop', '~> 0.40'
30
31
  end
@@ -5,6 +5,7 @@ module KumoDockerCloud
5
5
  class DockerCloudApi
6
6
  extend Forwardable
7
7
  def_delegators :@client, :services, :stacks
8
+ attr_reader :client
8
9
 
9
10
  def initialize(options = {})
10
11
  options[:username] ||= ENV['DOCKERCLOUD_USER']
@@ -1,6 +1,8 @@
1
1
  module KumoDockerCloud
2
2
  class Error < RuntimeError; end
3
3
  class ServiceDeployError < RuntimeError; end
4
+ class HaproxySocketError < RuntimeError; end
5
+ class HAProxyStateError < Error; end
4
6
  class EnvironmentApplyError < RuntimeError; end
5
7
  class StackCheckError < RuntimeError; end
6
8
  class InvalidStackError < RuntimeError; end
@@ -0,0 +1,24 @@
1
+ require 'json'
2
+
3
+ module KumoDockerCloud
4
+ class HaproxyCommand
5
+ def initialize(container_id, dc_client)
6
+ @container_id = container_id
7
+ @dc_client = dc_client
8
+ end
9
+
10
+ def execute(command)
11
+ cmd = %(sh -c "echo #{command} | nc -U /var/run/haproxy.stats")
12
+ api = DockerCloud::ContainerStreamAPI.new(@container_id, cmd, @dc_client.headers, @dc_client)
13
+
14
+ handler = KumoDockerCloud::HaproxyEventHandler.new
15
+ api.on(:open, &handler.on_open)
16
+ api.on(:message, &handler.on_message)
17
+ api.on(:error, &handler.on_error)
18
+ api.on(:close, &handler.on_close)
19
+
20
+ api.run!
21
+ handler.data
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,51 @@
1
+ require 'csv'
2
+
3
+ module KumoDockerCloud
4
+ class HaproxyContainer
5
+ def initialize(container_id, client)
6
+ @container_id = container_id
7
+ @client = client
8
+ end
9
+
10
+ def disable_server(server_name)
11
+ haproxy_server_name = haproxy_server_name(server_name)
12
+ HaproxyCommand.new(@container_id, @client).execute("disable server #{haproxy_server_name}")
13
+ end
14
+
15
+ def enable_server(server_name)
16
+ haproxy_server_name = haproxy_server_name(server_name)
17
+ HaproxyCommand.new(@container_id, @client).execute("enable server #{haproxy_server_name}")
18
+ end
19
+
20
+ private
21
+
22
+ def stats
23
+ haproxy_command = HaproxyCommand.new(@container_id, @client)
24
+ command_output = ''
25
+ retry_counter = 0
26
+ while command_output.empty? && retry_counter < 3
27
+ command_output = haproxy_command.execute('show stat')
28
+ retry_counter += 1
29
+ end
30
+ raise HAProxyStateError.new("Could not get stats from HAProxy backend") if command_output.empty?
31
+ CSV.parse(command_output, headers: true)
32
+ end
33
+
34
+ def haproxy_server_name(server_name)
35
+ current_stats = stats
36
+ haproxy_server_record = current_stats.find { |stat| prefix_match? stat, server_name }
37
+
38
+ raise HAProxyStateError.new("Unable to map #{server_name} to a HAProxy backend, I saw #{ get_server_names(current_stats) }") unless haproxy_server_record
39
+
40
+ "#{haproxy_server_record['# pxname']}/#{haproxy_server_record['svname']}"
41
+ end
42
+
43
+ def prefix_match?(stat_record, server_name)
44
+ stat_record["svname"].downcase.start_with? server_name.downcase.gsub('-', '_')
45
+ end
46
+
47
+ def get_server_names(stats_object)
48
+ stats_object.select { |record| record['# pxname'] == 'default_service' }.map { |record| record['svname']}.join(', ')
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ module KumoDockerCloud
2
+ class HaproxyEventHandler
3
+ attr_accessor :data
4
+
5
+ def initialize
6
+ @data = ''
7
+ end
8
+
9
+ def on_open
10
+ Proc.new { |_event| @data = '' }
11
+ end
12
+
13
+ def on_message
14
+ Proc.new { |event| @data << JSON.parse(event.data)['output'] }
15
+ end
16
+
17
+ def on_error
18
+ Proc.new { |event| raise HaproxySocketError.new(event.message) }
19
+ end
20
+
21
+ def on_close
22
+ Proc.new { |_event| EventMachine.stop }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module KumoDockerCloud
2
+ class HaproxyService < Service
3
+ def initialize(stack_name)
4
+ super(stack_name, 'haproxy')
5
+
6
+ @client = docker_cloud_api.client
7
+ end
8
+
9
+ def disable_service(service)
10
+ service_to_disable = service.name
11
+ haproxy_containers = containers.map { |container| HaproxyContainer.new(container.uuid, @client) }
12
+
13
+ raise KumoDockerCloud::HAProxyStateError.new('Could not get instances of the haproxy container for this environment') if haproxy_containers.empty?
14
+
15
+ haproxy_threads = haproxy_containers.map { |haproxy_container| Thread.new { haproxy_container.disable_server(service_to_disable) } }
16
+ haproxy_threads.each(&:join)
17
+ end
18
+
19
+ def enable_service(service)
20
+ service_to_enable = service.name
21
+ haproxy_containers = containers.map { |container| HaproxyContainer.new(container.uuid, @client) }
22
+
23
+ raise KumoDockerCloud::HAProxyStateError.new('Could not get instances of the haproxy container for this environment') if haproxy_containers.empty?
24
+
25
+ haproxy_threads = haproxy_containers.map { |haproxy_container| Thread.new { haproxy_container.enable_server(service_to_enable) } }
26
+ haproxy_threads.each(&:join)
27
+ end
28
+ end
29
+ end
@@ -30,6 +30,10 @@ module KumoDockerCloud
30
30
  get_service.linked_to_service
31
31
  end
32
32
 
33
+ def state
34
+ get_service.state
35
+ end
36
+
33
37
  def set_link(service_to_link, link_internal_name)
34
38
  linked_service = {
35
39
  to_service: service_to_link.resource_uri,
@@ -52,6 +56,10 @@ module KumoDockerCloud
52
56
  get_service.containers
53
57
  end
54
58
 
59
+ def uuid
60
+ get_service.uuid
61
+ end
62
+
55
63
  private
56
64
  attr_reader :stack_name
57
65
 
@@ -75,10 +83,6 @@ module KumoDockerCloud
75
83
  docker_cloud_api.service_by_stack_and_service_name(stack_name, name)
76
84
  end
77
85
 
78
- def uuid
79
- get_service.uuid
80
- end
81
-
82
86
  def image_name
83
87
  get_service.image_name.split(':').first
84
88
  end
@@ -1,3 +1,5 @@
1
+ require 'timeout'
2
+
1
3
  module KumoDockerCloud
2
4
  class Stack
3
5
  attr_reader :stack_name, :app_name, :options
@@ -18,37 +20,36 @@ module KumoDockerCloud
18
20
  checker.verify(service)
19
21
  end
20
22
 
23
+ def deploy_blue_green(service_names, version, checker = ServiceChecker.new)
24
+ haproxy_service = HaproxyService.new(@stack_name)
25
+
26
+ services = service_names.map { |name| Service.new(stack_name, name) }
27
+ ordered_deployment(services).each do |service|
28
+ begin
29
+ haproxy_service.disable_service(service) unless service.state == "Stopped"
30
+ service.deploy(version)
31
+ checker.verify(service)
32
+ haproxy_service.enable_service(service)
33
+ rescue HAProxyStateError => e
34
+ raise ServiceDeployError.new("Unable to place service #{service.name} into maintainance mode on HAProxy with message: #{e.message}")
35
+ rescue ServiceDeployError => e
36
+ haproxy_service.disable_service(service)
37
+ raise ServiceDeployError.new("Deployment or verification of service #{service.name} failed with message: #{e.message}")
38
+ end
39
+ end
40
+ end
41
+
21
42
  def services
22
43
  services = docker_cloud_api.services_by_stack_name(stack_name)
23
44
  services.map { |service| Service.new(stack_name, service.name) }
24
45
  end
25
46
 
26
- def deploy_blue_green(options)
27
- service_names = options[:service_names]
28
- version = options[:version]
29
- checker = options[:checker] || ServiceChecker.new
30
- switching_service_name = options[:switching_service_name]
31
-
32
- validate_params(version, "Version")
33
- validate_params(service_names, "Service names")
34
- validate_params(switching_service_name, "Switching service name")
35
-
36
- switching_service = Service.new(stack_name, switching_service_name)
37
- link = switching_service.links.find { |link| service_names.include?(Service.service_by_resource_uri(link[:to_service]).name) }
38
- active_service = Service.service_by_resource_uri(link[:to_service])
39
-
40
- inactive_service_name = service_names.find { |name| name != active_service.name }
41
- inactive_service = Service.new(stack_name, inactive_service_name)
42
-
43
- inactive_service.deploy(version)
44
- checker.verify(inactive_service)
47
+ private
45
48
 
46
- switching_service.set_link(inactive_service, link[:name])
47
- active_service.stop
49
+ def ordered_deployment(services)
50
+ services.sort { |service_a, service_b| service_b.state <=> service_a.state }
48
51
  end
49
52
 
50
- private
51
-
52
53
  def validate_params(param_value, param_name)
53
54
  raise KumoDockerCloud::Error.new("#{param_name} cannot be nil") unless param_value
54
55
  raise KumoDockerCloud::Error.new("#{param_name} cannot be empty") if param_value.empty?
@@ -1,3 +1,3 @@
1
1
  module KumoDockerCloud
2
- VERSION = '3.2.0'
2
+ VERSION = '3.3.0'
3
3
  end
@@ -7,3 +7,7 @@ require 'kumo_dockercloud/service_checker'
7
7
  require 'kumo_dockercloud/service_check'
8
8
  require 'kumo_dockercloud/errors'
9
9
  require 'kumo_dockercloud/console_jockey'
10
+ require 'kumo_dockercloud/haproxy_service'
11
+ require 'kumo_dockercloud/haproxy_container'
12
+ require 'kumo_dockercloud/haproxy_command'
13
+ require 'kumo_dockercloud/haproxy_event_handler'
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe KumoDockerCloud::HaproxyCommand do
4
+ describe '#execute' do
5
+ let(:command) { 'enable' }
6
+ let(:dc_client) { double(:dc_client, headers: nil)}
7
+ let(:container_id) { 'id' }
8
+ let(:api) { instance_double(DockerCloud::ContainerStreamAPI, on: nil, run!: nil)}
9
+ let(:cmd) { %(sh -c "echo #{command} | nc -U /var/run/haproxy.stats") }
10
+ let(:handler) { KumoDockerCloud::HaproxyEventHandler.new }
11
+
12
+
13
+ subject { described_class.new(container_id, dc_client).execute(command) }
14
+
15
+ it 'uses the ContainerStreamAPI with the passed in command' do
16
+ expect(DockerCloud::ContainerStreamAPI).to receive(:new).with(container_id, cmd, dc_client.headers, dc_client).and_return(api)
17
+ subject
18
+ end
19
+
20
+ before do
21
+ allow(DockerCloud::ContainerStreamAPI).to receive(:new).with(container_id, cmd, dc_client.headers, dc_client).and_return(api)
22
+ end
23
+
24
+ it 'configures the callback handlers' do
25
+ allow(KumoDockerCloud::HaproxyEventHandler).to receive(:new).and_return(handler)
26
+
27
+ expect(api).to receive(:on).with(:open)
28
+ expect(api).to receive(:on).with(:message)
29
+ expect(api).to receive(:on).with(:error)
30
+ expect(api).to receive(:on).with(:close)
31
+ subject
32
+ end
33
+
34
+ it 'runs the event machine' do
35
+ expect(api).to receive(:run!)
36
+ subject
37
+ end
38
+
39
+ it 'returns the data from the callback handler' do
40
+ allow(KumoDockerCloud::HaproxyEventHandler).to receive(:new).and_return(handler)
41
+ expect(handler).to receive(:data)
42
+ subject
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe KumoDockerCloud::HaproxyContainer do
4
+ subject { KumoDockerCloud::HaproxyContainer.new('container-id', client) }
5
+
6
+ let(:client) { instance_double(DockerCloud::Client) }
7
+ let(:haproxy_command) { instance_double(KumoDockerCloud::HaproxyCommand, :haproxy_command, execute: nil )}
8
+ let(:csv_output) do <<EOF
9
+ # pxname,svname
10
+ default_frontend,FRONTEND
11
+ default_service,BLUE_SERVICE_1
12
+ default_service,GREEN_SERVICE
13
+ default_service,BACKEND
14
+ EOF
15
+ end
16
+ let(:haproxy_server_name) { 'default_service/BLUE_SERVICE_1' }
17
+ let(:server_name) { 'blue-service' }
18
+ let(:non_existant_server_name) { 'derpy-service' }
19
+
20
+ before do
21
+ allow(KumoDockerCloud::HaproxyCommand).to receive(:new).and_return(haproxy_command)
22
+ allow(haproxy_command).to receive(:execute).with('show stat').and_return(csv_output)
23
+ end
24
+
25
+ describe '#disable_server' do
26
+ it "runs disable server using HAProxy's name" do
27
+ expect(haproxy_command).to receive(:execute).with("disable server #{haproxy_server_name}")
28
+ subject.disable_server(server_name)
29
+ end
30
+
31
+ it 'raises an error if it is unable to map a server name to a haproxy name' do
32
+ expect { subject.disable_server(non_existant_server_name) }.to raise_error(
33
+ KumoDockerCloud::HAProxyStateError,
34
+ "Unable to map #{non_existant_server_name} to a HAProxy backend, I saw BLUE_SERVICE_1, GREEN_SERVICE, BACKEND"
35
+ )
36
+ end
37
+
38
+ it 'tries 3 times if it is unable to get stats from haproxy' do
39
+ expect(haproxy_command).to receive(:execute).with('show stat').exactly(3).times.and_return('')
40
+ expect { subject.disable_server(server_name) }.to raise_error(
41
+ KumoDockerCloud::HAProxyStateError,
42
+ "Could not get stats from HAProxy backend"
43
+ )
44
+ end
45
+
46
+ it 'stops trying getting haproxy server name when it gets the haproxy record' do
47
+ expect(haproxy_command).to receive(:execute).with('show stat').once.ordered.and_return('')
48
+ expect(haproxy_command).to receive(:execute).with('show stat').once.ordered.and_return(csv_output)
49
+
50
+ expect(haproxy_command).to receive(:execute).with("disable server #{haproxy_server_name}").once.ordered
51
+ subject.disable_server(server_name)
52
+ end
53
+ end
54
+
55
+ describe '#enable_server' do
56
+ it "runs enable server using HAProxy's name" do
57
+ expect(haproxy_command).to receive(:execute).with("enable server #{haproxy_server_name}")
58
+ subject.enable_server(server_name)
59
+ end
60
+
61
+ it 'raises an error if it is unable to map a server name to a haproxy name' do
62
+ expect { subject.disable_server(non_existant_server_name) }.to raise_error(
63
+ KumoDockerCloud::HAProxyStateError,
64
+ "Unable to map #{non_existant_server_name} to a HAProxy backend, I saw BLUE_SERVICE_1, GREEN_SERVICE, BACKEND"
65
+ )
66
+ end
67
+
68
+ it 'tries 3 times if it is unable to get stats from haproxy' do
69
+ expect(haproxy_command).to receive(:execute).with('show stat').exactly(3).times.and_return('')
70
+ expect { subject.enable_server(server_name) }.to raise_error(
71
+ KumoDockerCloud::HAProxyStateError,
72
+ "Could not get stats from HAProxy backend"
73
+ )
74
+ end
75
+
76
+ it 'stops trying getting haproxy server name when it gets the haproxy record' do
77
+ expect(haproxy_command).to receive(:execute).with('show stat').once.ordered.and_return('')
78
+ expect(haproxy_command).to receive(:execute).with('show stat').once.ordered.and_return(csv_output)
79
+
80
+ expect(haproxy_command).to receive(:execute).with("enable server #{haproxy_server_name}").once.ordered
81
+ subject.enable_server(server_name)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe KumoDockerCloud::HaproxyEventHandler do
4
+ subject { described_class.new }
5
+
6
+ describe '#on_open' do
7
+ it 'resets the handler state' do
8
+ subject.data = 'fred'
9
+ subject.on_open.call(nil)
10
+ expect(subject.data).to eq ''
11
+ end
12
+ end
13
+
14
+ describe '#on_message' do
15
+ let(:event) { double(:event, data: '{"output": "data"}')}
16
+
17
+ it 'parses json' do
18
+ expect(JSON).to receive(:parse).with(event.data).and_return({'output' => 'data'})
19
+ subject.on_message.call(event)
20
+ end
21
+
22
+ it 'accumulate the message to data' do
23
+ subject.on_message.call(event)
24
+ subject.on_message.call(event)
25
+ subject.on_message.call(event)
26
+ expect(subject.data).to eq('datadatadata')
27
+ end
28
+ end
29
+
30
+ describe '#on_error' do
31
+ let(:event) { double(:event, message: 'woops!') }
32
+
33
+ it 'raises a HaproxySocketError with the correct message' do
34
+ expect { subject.on_error.call(event) }.to raise_error(KumoDockerCloud::HaproxySocketError, 'woops!')
35
+ end
36
+ end
37
+
38
+ describe '#on_close' do
39
+ it 'closes the event machine' do
40
+ expect(EventMachine).to receive(:stop)
41
+ subject.on_close.call(nil)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe KumoDockerCloud::HaproxyService do
4
+ let(:service_name) { 'the-service' }
5
+ let(:container_uuid) { 'i-am-the-container'}
6
+ let(:docker_cloud_container) { double(:docker_cloud_haproxy, uuid: container_uuid)}
7
+ let(:containers) { [docker_cloud_container] }
8
+ let(:docker_cloud_service) { double(:service, containers: containers) }
9
+ let(:haproxy_container) { instance_double(KumoDockerCloud::HaproxyContainer, :haproxy_container, disable_server: nil, enable_server: nil) }
10
+ let(:docker_cloud_client) { double }
11
+ let(:kumo_docker_cloud_api) { instance_double(KumoDockerCloud::DockerCloudApi, :docker_cloud_api, client: docker_cloud_client ) }
12
+
13
+ before do
14
+ allow(KumoDockerCloud::DockerCloudApi).to receive(:new).and_return(kumo_docker_cloud_api)
15
+ allow(kumo_docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', 'haproxy').and_return(docker_cloud_service)
16
+ allow(KumoDockerCloud::HaproxyContainer).to receive(:new).with(container_uuid, docker_cloud_client).and_return(haproxy_container)
17
+ end
18
+
19
+ describe '#disable_service' do
20
+ subject { KumoDockerCloud::HaproxyService.new('stack_name').disable_service(service_to_disable) }
21
+ let(:service_to_disable) { instance_double(KumoDockerCloud::Service, :service_to_disable, name: service_name) }
22
+
23
+ it 'sends the disable_server message to all its containers' do
24
+ expect(haproxy_container).to receive(:disable_server).with(service_name)
25
+ subject
26
+ end
27
+
28
+ it 'blows up when there are no instances of haproxy' do
29
+ allow(docker_cloud_service).to receive(:containers).and_return([])
30
+ expect { subject }.to raise_error(KumoDockerCloud::HAProxyStateError, 'Could not get instances of the haproxy container for this environment')
31
+ end
32
+ end
33
+
34
+ describe '#enable_service' do
35
+ subject { KumoDockerCloud::HaproxyService.new('stack_name').enable_service(service_to_enable) }
36
+ let(:service_to_enable) { instance_double(KumoDockerCloud::Service, :service_to_enable, name: service_name) }
37
+
38
+ it 'sends the enable_server message to all its containers' do
39
+ expect(haproxy_container).to receive(:enable_server).with(service_name)
40
+ subject
41
+ end
42
+
43
+ it 'blows up when there are no instances of haproxy' do
44
+ allow(docker_cloud_service).to receive(:containers).and_return([])
45
+ expect { subject }.to raise_error(KumoDockerCloud::HAProxyStateError, 'Could not get instances of the haproxy container for this environment')
46
+ end
47
+ end
48
+ end
@@ -67,75 +67,65 @@ describe KumoDockerCloud::Stack do
67
67
  end
68
68
 
69
69
  describe '#deploy_blue_green' do
70
- let(:service_name) { 'test_service' }
71
- let(:version) { '1' }
72
- let(:checker) { instance_double(KumoDockerCloud::ServiceChecker, verify: nil) }
73
-
74
- let(:active_service) { instance_double(KumoDockerCloud::Service, :active_service, name: "service-a", stop: nil) }
75
- let(:inactive_service) { instance_double(KumoDockerCloud::Service, :inactive_service, name: "service-b", deploy: nil) }
76
- let(:service_link) { { name: switching_service_internal_link_name, to_service: linked_service_uri} }
77
- let(:linked_service_uri) { "active_uri" }
78
- let(:switching_service) { instance_double(KumoDockerCloud::Service, links: [service_link], set_link: nil) }
79
- let(:switching_service_internal_link_name) { "app" }
80
- let(:deploy_options) do
81
- {
82
- service_names: [active_service.name, inactive_service.name],
83
- version: version,
84
- checker: checker,
85
- switching_service_name: "switcher"
86
- }
87
- end
88
-
89
- subject { described_class.new(app_name, environment_name).deploy_blue_green(deploy_options) }
70
+ subject { stack.deploy_blue_green(service_names, version, checker) }
71
+ let(:checker) { instance_double(KumoDockerCloud::ServiceChecker, :service_checker, verify: true) }
72
+ let(:service_names) { ['service-a', 'service-b'] }
73
+ let(:version) { 1 }
74
+ let(:service_a) { instance_double(KumoDockerCloud::Service, :service_a, state: 'Running', deploy: nil, name: 'service_a') }
75
+ let(:service_b) { instance_double(KumoDockerCloud::Service, :service_b, state: 'Running', deploy: nil, name: 'service_b') }
76
+ let(:haproxy) { instance_double(KumoDockerCloud::HaproxyService, :haproxy_svc, disable_service: nil, enable_service: nil) }
90
77
 
91
78
  before do
92
- allow(KumoDockerCloud::Service).to receive(:service_by_resource_uri).with(linked_service_uri).and_return(active_service)
93
-
94
- allow(KumoDockerCloud::Service).to receive(:new)
95
- .with(stack_name, active_service.name)
96
- .and_return(active_service)
97
-
98
- allow(KumoDockerCloud::Service).to receive(:new)
99
- .with(stack_name, inactive_service.name)
100
- .and_return(inactive_service)
101
-
102
- allow(KumoDockerCloud::Service).to receive(:new)
103
- .with(stack_name, deploy_options[:switching_service_name])
104
- .and_return(switching_service)
79
+ allow(KumoDockerCloud::Service).to receive(:new).with(stack_name, 'service-a').and_return(service_a)
80
+ allow(KumoDockerCloud::Service).to receive(:new).with(stack_name, 'service-b').and_return(service_b)
81
+ allow(KumoDockerCloud::HaproxyService).to receive(:new).with(stack_name).and_return(haproxy)
105
82
  end
106
83
 
107
- context 'when parameters are missing' do
108
- it 'blows up when version is missing' do
109
- deploy_options.delete(:version)
110
- expect{ subject }.to raise_error(KumoDockerCloud::Error, "Version cannot be nil")
111
- end
84
+ it 'deploys to each service when both are active' do
85
+ expect(haproxy).to receive(:disable_service).with(service_a)
86
+ expect(haproxy).to receive(:disable_service).with(service_b)
87
+ expect(service_a).to receive(:deploy).with(version)
88
+ expect(service_b).to receive(:deploy).with(version)
89
+ subject
90
+ end
112
91
 
113
- it 'blows up when service_names are missing' do
114
- deploy_options.delete(:service_names)
115
- expect{ subject }.to raise_error(KumoDockerCloud::Error, "Service names cannot be nil")
116
- end
92
+ it 'deploys to the stopped service first when one is inactive' do
93
+ allow(service_b).to receive(:state).and_return('Stopped')
117
94
 
118
- it 'blows up when switching_service_name is missing' do
119
- deploy_options.delete(:switching_service_name)
120
- expect{ subject }.to raise_error(KumoDockerCloud::Error, "Switching service name cannot be nil")
121
- end
95
+ expect(haproxy).to_not receive(:disable_service).with(service_b)
96
+ expect(service_b).to receive(:deploy).with(version).ordered
97
+ expect(haproxy).to receive(:disable_service).with(service_a).ordered
98
+ expect(service_a).to receive(:deploy).with(version).ordered
99
+ subject
122
100
  end
123
101
 
124
- it 'deploys to the blue service only' do
125
- expect(inactive_service).to receive(:deploy).with(version)
126
- expect(checker).to receive(:verify).with(inactive_service)
127
- expect(active_service).to_not receive(:deploy)
102
+ it 'runs the check on each service' do
103
+ expect(checker).to receive(:verify).with(service_a)
104
+ expect(checker).to receive(:verify).with(service_b)
128
105
  subject
129
106
  end
130
107
 
131
- it 'switches over to the blue service on a successful deployment' do
132
- expect(switching_service).to receive(:set_link).with(inactive_service, switching_service_internal_link_name)
108
+ it 're-enables each service in haproxy' do
109
+ expect(haproxy).to receive(:enable_service).with(service_a)
110
+ expect(haproxy).to receive(:enable_service).with(service_b)
133
111
  subject
134
112
  end
135
113
 
136
- it 'shuts down the previously green service' do
137
- expect(active_service).to receive(:stop)
138
- subject
114
+ it 'cancels deployment if the first deploy fails' do
115
+ allow(checker).to receive(:verify).with(service_a).and_raise(KumoDockerCloud::ServiceDeployError)
116
+ expect(service_b).to_not receive(:deploy)
117
+ expect { subject }.to raise_error(KumoDockerCloud::ServiceDeployError)
118
+ end
119
+
120
+ it 'raises an error if any attempt to place a service into maintainance mode fails' do
121
+ expect(haproxy).to receive(:disable_service).with(service_a).and_raise(KumoDockerCloud::HAProxyStateError, 'Something broke!')
122
+ expect { subject }.to raise_error(KumoDockerCloud::ServiceDeployError)
123
+ end
124
+
125
+ it 'attempts to place a service into maintainance mode if deployment fails because HAProxy will take it out of maintainance mode on deploy' do
126
+ expect(haproxy).to receive(:disable_service).with(service_a).twice
127
+ allow(checker).to receive(:verify).with(service_a).and_raise(KumoDockerCloud::ServiceDeployError)
128
+ expect { subject }.to raise_error(KumoDockerCloud::ServiceDeployError)
139
129
  end
140
130
  end
141
131
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumo_dockercloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Redbubble
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-05-30 00:00:00.000000000 Z
13
+ date: 2016-06-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: httpi
@@ -110,6 +110,20 @@ dependencies:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
112
  version: '1.22'
113
+ - !ruby/object:Gem::Dependency
114
+ name: pry-byebug
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '3.4'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - "~>"
125
+ - !ruby/object:Gem::Version
126
+ version: '3.4'
113
127
  - !ruby/object:Gem::Dependency
114
128
  name: rubocop
115
129
  requirement: !ruby/object:Gem::Requirement
@@ -149,6 +163,10 @@ files:
149
163
  - lib/kumo_dockercloud/environment.rb
150
164
  - lib/kumo_dockercloud/environment_config.rb
151
165
  - lib/kumo_dockercloud/errors.rb
166
+ - lib/kumo_dockercloud/haproxy_command.rb
167
+ - lib/kumo_dockercloud/haproxy_container.rb
168
+ - lib/kumo_dockercloud/haproxy_event_handler.rb
169
+ - lib/kumo_dockercloud/haproxy_service.rb
152
170
  - lib/kumo_dockercloud/service.rb
153
171
  - lib/kumo_dockercloud/service_check.rb
154
172
  - lib/kumo_dockercloud/service_checker.rb
@@ -169,6 +187,10 @@ files:
169
187
  - spec/kumo_dockercloud/docker_cloud_api_spec.rb
170
188
  - spec/kumo_dockercloud/environment_config_spec.rb
171
189
  - spec/kumo_dockercloud/environment_spec.rb
190
+ - spec/kumo_dockercloud/haproxy_command_spec.rb
191
+ - spec/kumo_dockercloud/haproxy_container_spec.rb
192
+ - spec/kumo_dockercloud/haproxy_event_handler_spec.rb
193
+ - spec/kumo_dockercloud/haproxy_service_spec.rb
172
194
  - spec/kumo_dockercloud/service_checker_spec.rb
173
195
  - spec/kumo_dockercloud/service_spec.rb
174
196
  - spec/kumo_dockercloud/stack_checker_spec.rb
@@ -210,6 +232,10 @@ test_files:
210
232
  - spec/kumo_dockercloud/docker_cloud_api_spec.rb
211
233
  - spec/kumo_dockercloud/environment_config_spec.rb
212
234
  - spec/kumo_dockercloud/environment_spec.rb
235
+ - spec/kumo_dockercloud/haproxy_command_spec.rb
236
+ - spec/kumo_dockercloud/haproxy_container_spec.rb
237
+ - spec/kumo_dockercloud/haproxy_event_handler_spec.rb
238
+ - spec/kumo_dockercloud/haproxy_service_spec.rb
213
239
  - spec/kumo_dockercloud/service_checker_spec.rb
214
240
  - spec/kumo_dockercloud/service_spec.rb
215
241
  - spec/kumo_dockercloud/stack_checker_spec.rb