kumo_dockercloud 3.2.0 → 3.3.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: 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