conan_deploy 0.0.4 → 0.0.5

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.
@@ -0,0 +1,118 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+
4
+ module RestClientExt
5
+ include RestClient
6
+
7
+ RETRIES = 3
8
+
9
+ def self.handle_network_error(verb, err, url, tries)
10
+ additional_tries = tries - 1
11
+ message = "#{verb} to #{url} failed with (#{err.ai}), retrying #{additional_tries} more times"
12
+
13
+ case err
14
+ when Errno::ETIMEDOUT, Errno::ECONNREFUSED, RestClient::ServerBrokeConnection, RestClient::RequestTimeout
15
+ puts message
16
+ sleep 1
17
+ else
18
+ raise err
19
+ end
20
+
21
+ additional_tries
22
+ end
23
+
24
+ def self.request_options(verb, url, headers, payload = nil)
25
+ opts = {
26
+ :method => verb.downcase.to_sym,
27
+ :url => url,
28
+ :headers => headers.clone,
29
+ :timeout => headers[:timeout] || 10,
30
+ :open_timeout => headers[:open_timeout] || 10
31
+ }
32
+
33
+ payload ? opts.merge(:payload => payload) : opts
34
+ end
35
+
36
+ def self.execute_request(verb, url, headers, payload = nil, tries = RETRIES, &block)
37
+ Request.execute(request_options(verb, url, headers, payload),&block)
38
+ rescue SystemCallError, Exception => err
39
+ retry unless (tries = handle_network_error(verb, err, url, tries)).zero?
40
+ end
41
+
42
+ def self.get(url, headers={}, &block)
43
+ execute_request("GET", url, headers, &block)
44
+ end
45
+
46
+ def self.post(url, payload, headers={}, &block)
47
+ execute_request("POST", url, headers, payload, &block)
48
+ end
49
+
50
+ def self.put(url, payload, headers={}, &block)
51
+ execute_request("PUT", url, headers, payload, &block)
52
+ end
53
+
54
+ def self.delete(url, headers={}, &block)
55
+ execute_request("DELETE", url, headers, &block)
56
+ end
57
+ end
58
+
59
+ module ApiHelper
60
+
61
+ def self.defaultHeaders(headers = {})
62
+ default_headers = {
63
+ :user_agent => 'CONAN_DA_DEPLOYER',
64
+ :accept => :json,
65
+ :accept_encoding => :gzip,
66
+ :content_type => :json }
67
+
68
+ default_headers = default_headers.merge(headers).delete_if { |k, v| v.nil? }
69
+ return default_headers
70
+ end
71
+
72
+ def self.healthcheck(url, timeout)
73
+ RestClientExt.get(
74
+ url,
75
+ defaultHeaders(
76
+ :timeout => timeout,
77
+ :open_timeout => timeout)) do |response, request, result, &block|
78
+ return NexusResponse.new(response.code, response.body)
79
+ end
80
+ end
81
+ end
82
+
83
+ class NexusResponse
84
+ attr_accessor :code
85
+ attr_accessor :json
86
+ attr_accessor :raw_json
87
+
88
+ def initialize(code, raw_json)
89
+ @code = code
90
+
91
+ begin
92
+ @json = OpenStruct.new(JSON.parse(Zlib::GzipReader.new(StringIO.new(raw_json)).read))
93
+ @raw_json = Zlib::GzipReader.new(StringIO.new(raw_json)).read
94
+ rescue Zlib::Error
95
+ begin
96
+ @json = OpenStruct.new(JSON.parse(raw_json))
97
+ @raw_json = raw_json
98
+ rescue JSON::ParserError
99
+ puts "NexusResponse - could not parse following JSON:\n#{raw_json}"
100
+ @json = OpenStruct.new(JSON.parse("{ \"status\" : \"error\", \"errorMessage\" : \"Previous call likely returned a Stackato HTML error page\" }"))
101
+ @raw_json = raw_json
102
+ end
103
+ rescue Zlib::GzipFile::Error
104
+ begin
105
+ @json = OpenStruct.new(JSON.parse(raw_json))
106
+ @raw_json = raw_json
107
+ rescue JSON::ParserError
108
+ puts "NexusResponse - could not parse following JSON:\n#{raw_json}"
109
+ @json = OpenStruct.new(JSON.parse("{ \"status\" : \"error\", \"errorMessage\" : \"Previous call likely returned a Stackato HTML error page\" }"))
110
+ @raw_json = raw_json
111
+ end
112
+ rescue JSON::ParserError
113
+ puts "NexusResponse - could not parse following JSON:\n#{raw_json}"
114
+ @json = OpenStruct.new(JSON.parse("{ \"status\" : \"error\", \"errorMessage\" : \"Previous call likely returned a Stackato HTML error page\" }"))
115
+ @raw_json = raw_json
116
+ end
117
+ end
118
+ end
data/lib/conan/deploy.rb CHANGED
@@ -36,8 +36,13 @@ module Conan
36
36
  manifest.provision
37
37
  manifest.bg_configure
38
38
  manifest.bg_deploy
39
- manifest.bg_switch
40
- manifest.bg_clean
39
+ if(manifest.is_inactive_node_healthy?)
40
+ manifest.bg_switch
41
+ manifest.bg_clean
42
+ else
43
+ raise RuntimeError.new "Blue node is not healthy. Skipping switch and cleaning to facilitate troubleshooting."
44
+ end
45
+
41
46
  else
42
47
  raise ArgumentError.new "Invalid action: #{options[:action]}"
43
48
  end
@@ -1,6 +1,5 @@
1
1
  require 'rest_client'
2
2
  require 'rexml/document'
3
- require 'properties-ruby'
4
3
  require 'json'
5
4
  require 'securerandom'
6
5
  require 'daphne_util'
@@ -9,6 +8,7 @@ require 'conan/output'
9
8
  require 'conan/repository'
10
9
  require 'conan/stackato'
11
10
  require 'conan/newrelic'
11
+ require 'conan/application_helper'
12
12
 
13
13
  module ManifestBuilder
14
14
  def self.build(options, pipeline_id, env_id, artifact_repo=nil, outthingy=nil, &block)
@@ -119,6 +119,12 @@ class Manifest
119
119
  @current_pipeline.bg_deploy(@current_environment, paas, force)
120
120
  end
121
121
 
122
+ def is_inactive_node_healthy?
123
+ validateThatEnvironmentIsSelected
124
+ puts "Blue/green health check #{@current_environment.id} for #{@current_pipeline.id} pipeline"
125
+ @current_pipeline.is_inactive_node_healthy?(@current_environment, paas)
126
+ end
127
+
122
128
  def bg_switch
123
129
  validateThatEnvironmentIsSelected
124
130
  puts "Blue/green switch #{@current_environment.id} for #{@current_pipeline.id} pipeline"
@@ -141,6 +147,8 @@ class Manifest
141
147
  end
142
148
 
143
149
  class Pipeline
150
+ include ApiHelper
151
+
144
152
  attr_accessor :id, :apps
145
153
 
146
154
  def initialize(id, artifact_repo, output)
@@ -219,6 +227,19 @@ class Pipeline
219
227
  }
220
228
  end
221
229
 
230
+ def is_inactive_node_healthy?(environment, paas)
231
+ eachAppDeployment(environment) { |app, deploy|
232
+ response = ApiHelper.healthcheck(deploy.inactive_healthcheck_url(app.id), 60)
233
+
234
+ if(response.code != 200)
235
+ puts "Healthcheck failed for inactive (Blue) node. Here is the entire response: #{response.inspect}"
236
+ return false
237
+ else
238
+ return true
239
+ end
240
+ }
241
+ end
242
+
222
243
  def bg_switch(environment, paas)
223
244
  eachAppDeployment(environment) { |app, deploy| paas.bg_switch(app, deploy) }
224
245
  end
@@ -329,6 +350,7 @@ class Application
329
350
  res
330
351
  end
331
352
 
353
+
332
354
  def compareVersion(target_version, deployed_version)
333
355
  target = Gem::Version.new(target_version)
334
356
  begin
@@ -478,6 +500,14 @@ class Deployment < HasOptions
478
500
  "http://#{dns_name(app_id)}/status/manifest"
479
501
  end
480
502
 
503
+ def active_healthcheck_url(app_id)
504
+ "http://#{dns_name(app_id)}/status/healthcheck"
505
+ end
506
+
507
+ def inactive_healthcheck_url(app_id)
508
+ "http://inactive.#{dns_name(app_id)}/status/healthcheck"
509
+ end
510
+
481
511
  def paas_target
482
512
  "https://api.paas.#{@ship}.#{@org}.#{@@paas_domain}"
483
513
  end
data/lib/conan/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Conan
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conan_deploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-04-16 00:00:00.000000000 Z
13
+ date: 2014-04-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: daphne_util
@@ -36,6 +36,7 @@ extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
38
  - bin/conan
39
+ - lib/conan/application_helper.rb
39
40
  - lib/conan/deploy.rb
40
41
  - lib/conan/manifest_builder.rb
41
42
  - lib/conan/newrelic.rb