nomade 0.1.3 → 0.1.4

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
  SHA256:
3
- metadata.gz: d592e96d768d830096aeaf2c1af8abea0e509582895d661c2c25ac718b8b1885
4
- data.tar.gz: 711a78ece036b6779792159789501e7b542dcca29b1f7ff2ae78a8eee3cbd55f
3
+ metadata.gz: 123b90eb6930f46b12b75aee376034b51c623ae5e788149bcff15ca2dccaf8b9
4
+ data.tar.gz: a0aef251645a73ee2fb61f316298c354316d27e02a2cba59bb3c22e113e06acb
5
5
  SHA512:
6
- metadata.gz: 808441c9d68364ffa0e0eca8fdcd1fd8e70a0c1acb2bbf3397839ae0dfbf1b61c58dc03452fc92038c01de924c46d46009a665680996b8647671d713a6c60757
7
- data.tar.gz: ea50eaf4bb6687334a194b0b805662d51a2be91baa768f68d403a7a4bb897f3b2b1215a4d2d1da98405b64ce5a4acb6bebc64c752c5ae61ddee92ee1405739d7
6
+ metadata.gz: 7a0dd940d4b32facd28d3e81a2fdb49d97cc0a43205e93997d913d316101902d9922b8e87bca59bd25b84752d1535a19a60b622a1cf85ecd2baf8ae47f965ea6
7
+ data.tar.gz: 4598198f4be75e00c44f6bf9a0185b013e135ceead95dc25d6ccef0de6bf13f5913cf699fb45f9b3f8a6aaaa7b7cad008366cdc3f131c2c06ec982ec2b6875eb
@@ -1,3 +1,5 @@
1
+ require "base64"
2
+
1
3
  module Nomade
2
4
  class Deployer
3
5
  attr_reader :nomad_job
@@ -14,6 +16,10 @@ module Nomade
14
16
  Nomade::Hooks::DEPLOY_RUNNING => [],
15
17
  Nomade::Hooks::DEPLOY_FINISHED => [],
16
18
  Nomade::Hooks::DEPLOY_FAILED => [],
19
+
20
+ Nomade::Hooks::DISPATCH_RUNNING => [],
21
+ Nomade::Hooks::DISPATCH_FINISHED => [],
22
+ Nomade::Hooks::DISPATCH_FAILED => [],
17
23
  }
18
24
  add_hook(Nomade::Hooks::DEPLOY_FAILED, lambda {|hook_type, nomad_job, messages|
19
25
  @logger.error "Failing deploy:"
@@ -21,6 +27,12 @@ module Nomade
21
27
  @logger.error "- #{message}"
22
28
  end
23
29
  })
30
+ add_hook(Nomade::Hooks::DISPATCH_FAILED, lambda {|hook_type, nomad_job, messages|
31
+ @logger.error "Failing dispatch:"
32
+ messages.each do |message|
33
+ @logger.error "- #{message}"
34
+ end
35
+ })
24
36
 
25
37
  self
26
38
  end
@@ -31,6 +43,15 @@ module Nomade
31
43
  @deployment_id = nil
32
44
 
33
45
  self
46
+ rescue Nomade::HttpConnectionError => e
47
+ [e.class.to_s, e.message, "Http connection error, exiting!"].compact.uniq.map{|l| @logger.error(l)}
48
+ exit(7)
49
+ rescue Nomade::HttpBadResponse => e
50
+ [e.class.to_s, e.message, "Http bad response, exiting!"].compact.uniq.map{|l| @logger.error(l)}
51
+ exit(8)
52
+ rescue Nomade::HttpBadContentType => e
53
+ [e.class.to_s, e.message, "Http unexpected content type!"].compact.uniq.map{|l| @logger.error(l)}
54
+ exit(9)
34
55
  end
35
56
 
36
57
  def add_hook(hook, hook_method)
@@ -40,12 +61,20 @@ module Nomade
40
61
  @hooks[Nomade::Hooks::DEPLOY_FINISHED] << hook_method
41
62
  elsif Nomade::Hooks::DEPLOY_FAILED == hook
42
63
  @hooks[Nomade::Hooks::DEPLOY_FAILED] << hook_method
64
+ elsif Nomade::Hooks::DISPATCH_RUNNING == hook
65
+ @hooks[Nomade::Hooks::DISPATCH_RUNNING] << hook_method
66
+ elsif Nomade::Hooks::DISPATCH_FINISHED == hook
67
+ @hooks[Nomade::Hooks::DISPATCH_FINISHED] << hook_method
68
+ elsif Nomade::Hooks::DISPATCH_FAILED == hook
69
+ @hooks[Nomade::Hooks::DISPATCH_FAILED] << hook_method
43
70
  else
44
71
  raise "#{hook} not supported!"
45
72
  end
46
73
  end
47
74
 
48
75
  def deploy!
76
+ check_for_job_init
77
+
49
78
  run_hooks(Nomade::Hooks::DEPLOY_RUNNING, @nomad_job, nil)
50
79
  _plan
51
80
  _deploy
@@ -67,6 +96,62 @@ module Nomade
67
96
  rescue Nomade::DeploymentFailedError => e
68
97
  run_hooks(Nomade::Hooks::DEPLOY_FAILED, @nomad_job, [e.class.to_s, e.message, "Couldn't deploy succesfully, exiting!"].compact.uniq)
69
98
  exit(6)
99
+ rescue Nomade::HttpConnectionError => e
100
+ run_hooks(Nomade::Hooks::DEPLOY_FAILED, @nomad_job, [e.class.to_s, e.message, "Http connection error, exiting!"].compact.uniq)
101
+ exit(7)
102
+ rescue Nomade::HttpBadResponse => e
103
+ run_hooks(Nomade::Hooks::DEPLOY_FAILED, @nomad_job, [e.class.to_s, e.message, "Http bad response, exiting!"].compact.uniq)
104
+ exit(8)
105
+ rescue Nomade::HttpBadContentType => e
106
+ run_hooks(Nomade::Hooks::DEPLOY_FAILED, @nomad_job, [e.class.to_s, e.message, "Http unexpected content type!"].compact.uniq)
107
+ exit(9)
108
+ end
109
+
110
+ def dispatch!(payload_data: nil, payload_metadata: {})
111
+ check_for_job_init
112
+
113
+ run_hooks(Nomade::Hooks::DISPATCH_RUNNING, @nomad_job, nil)
114
+ _dispatch(payload_data, payload_metadata)
115
+ run_hooks(Nomade::Hooks::DISPATCH_FINISHED, @nomad_job, nil)
116
+ rescue Nomade::DispatchMetaDataFormattingError => e
117
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Metadata wrongly formatted, exiting!"].compact.uniq)
118
+ exit(10)
119
+ rescue Nomade::DispatchMissingMetaData => e
120
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Required metadata missing, exiting!"].compact.uniq)
121
+ exit(11)
122
+ rescue Nomade::DispatchUnknownMetaData => e
123
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Unknown metadata sent to server, exiting!"].compact.uniq)
124
+ exit(12)
125
+ rescue Nomade::DispatchMissingPayload => e
126
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Job requires payload, but payload isn't set!"].compact.uniq)
127
+ exit(20)
128
+ rescue Nomade::DispatchPayloadNotAllowed => e
129
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Job does not allow payload!"].compact.uniq)
130
+ exit(21)
131
+ rescue Nomade::DispatchPayloadUnknown => e
132
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "API error!"].compact.uniq)
133
+ exit(22)
134
+ rescue Nomade::DispatchWrongJobType => e
135
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Job has wrong job-type"].compact.uniq)
136
+ exit(30)
137
+ rescue Nomade::DispatchNotParamaterized => e
138
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Job is not paramaterized"].compact.uniq)
139
+ exit(31)
140
+ rescue Nomade::AllocationFailedError => e
141
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Allocation failed with errors, exiting!"].compact.uniq)
142
+ exit(40)
143
+ rescue Nomade::GeneralError => e
144
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "GeneralError hit, exiting!"].compact.uniq)
145
+ exit(1)
146
+ rescue Nomade::HttpConnectionError => e
147
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Http connection error, exiting!"].compact.uniq)
148
+ exit(7)
149
+ rescue Nomade::HttpBadResponse => e
150
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Http bad response, exiting!"].compact.uniq)
151
+ exit(8)
152
+ rescue Nomade::HttpBadContentType => e
153
+ run_hooks(Nomade::Hooks::DISPATCH_FAILED, @nomad_job, [e.class.to_s, e.message, "Http unexpected content type!"].compact.uniq)
154
+ exit(9)
70
155
  end
71
156
 
72
157
  def stop!(purge = false)
@@ -75,6 +160,12 @@ module Nomade
75
160
 
76
161
  private
77
162
 
163
+ def check_for_job_init
164
+ unless @nomad_job
165
+ raise Nomade::GeneralError.new("Did you forget to run init_job?")
166
+ end
167
+ end
168
+
78
169
  def run_hooks(hook, job, messages)
79
170
  @hooks[hook].each do |hook_method|
80
171
  hook_method.call(hook, job, messages)
@@ -112,32 +203,36 @@ module Nomade
112
203
  @http.create_job(@nomad_job)
113
204
  end
114
205
 
115
- @logger.info "EvaluationID: #{@evaluation_id}"
116
- @logger.info "#{@evaluation_id} Waiting until evaluation is complete"
117
- eval_status = nil
118
- while(eval_status != "complete") do
119
- evaluation = @http.evaluation_request(@evaluation_id)
120
- @deployment_id ||= evaluation["DeploymentID"]
121
- eval_status = evaluation["Status"]
122
- @logger.info "."
123
- sleep(1)
124
- end
206
+ if @evaluation_id.empty?
207
+ @logger.info "Parameterized job without evaluation, no more work needed"
208
+ else
209
+ @logger.info "EvaluationID: #{@evaluation_id}"
210
+ @logger.info "#{@evaluation_id} Waiting until evaluation is complete"
211
+ eval_status = nil
212
+ while(eval_status != "complete") do
213
+ evaluation = @http.evaluation_request(@evaluation_id)
214
+ @deployment_id ||= evaluation["DeploymentID"]
215
+ eval_status = evaluation["Status"]
216
+ @logger.info "."
217
+ sleep(1)
218
+ end
125
219
 
126
- @logger.info "Waiting until allocations are no longer pending"
127
- allocations = @http.allocations_from_evaluation_request(@evaluation_id)
128
- until allocations.all?{|a| a["ClientStatus"] != "pending"}
129
- @logger.info "."
130
- sleep(2)
131
- allocations = @http.allocations_from_evaluation_request(@evaluation_id)
132
- end
220
+ @logger.info "Waiting until allocations are no longer pending"
221
+ allocations = []
222
+ until !allocations.empty? && allocations.all?{|a| a["ClientStatus"] != "pending"}
223
+ @logger.info "."
224
+ sleep(2)
225
+ allocations = @http.allocations_from_evaluation_request(@evaluation_id)
226
+ end
133
227
 
134
- case @nomad_job.job_type
135
- when "service"
136
- service_deploy
137
- when "batch"
138
- batch_deploy
139
- else
140
- raise Nomade::GeneralError.new("Job-type '#{@nomad_job.job_type}' not implemented")
228
+ case @nomad_job.job_type
229
+ when "service"
230
+ service_deploy
231
+ when "batch"
232
+ batch_deploy
233
+ else
234
+ raise Nomade::GeneralError.new("Job-type '#{@nomad_job.job_type}' not implemented")
235
+ end
141
236
  end
142
237
  rescue Nomade::AllocationFailedError => e
143
238
  e.allocations.each do |allocation|
@@ -187,6 +282,96 @@ module Nomade
187
282
  raise
188
283
  end
189
284
 
285
+ def _dispatch(payload_data, payload_metadata)
286
+ @logger.info "Dispatching #{@nomad_job.job_name} (#{@nomad_job.job_type}) with #{@nomad_job.image_name_and_version}"
287
+ @logger.info "URL: #{@nomad_endpoint}/ui/jobs/#{@nomad_job.job_name}"
288
+
289
+ @logger.info "Running sanity checks.."
290
+
291
+ if @nomad_job.job_type != "batch"
292
+ raise DispatchWrongJobType.new("Job-type for #{@nomad_job.job_name} is \"#{@nomad_job.job_type}\" but should be \"batch\"")
293
+ end
294
+
295
+ if @nomad_job.configuration(:hash)["ParameterizedJob"] == nil
296
+ raise DispatchNotParamaterized.new("Job doesn't seem to be a paramaterized job, returned JobHash doesn't contain ParameterizedJob-key")
297
+ end
298
+
299
+ payload_data = if payload_data
300
+ Base64.encode64(payload_data)
301
+ else
302
+ nil
303
+ end
304
+
305
+ payload_metadata = if payload_metadata == nil
306
+ {}
307
+ else
308
+ Hash[payload_metadata.collect{|k,v| [k.to_s, v]}]
309
+ payload_metadata.each do |key, value|
310
+ unless [key, value].map(&:class) == [String, String]
311
+ raise Nomade::DispatchMetaDataFormattingError.new("Dispatch metadata must only be strings: #{key}(#{key.class}) = #{value}(#{value.class})")
312
+ end
313
+ end
314
+ end
315
+
316
+ meta_required = @nomad_job.configuration(:hash)["ParameterizedJob"]["MetaRequired"]
317
+ meta_optional = @nomad_job.configuration(:hash)["ParameterizedJob"]["MetaOptional"]
318
+ payload = @nomad_job.configuration(:hash)["ParameterizedJob"]["Payload"]
319
+
320
+ if meta_required
321
+ @logger.info "Dispatch job expects the following metakeys: #{meta_required.join(", ")}"
322
+ meta_required.each do |required_key|
323
+ unless payload_metadata.keys.include?(required_key)
324
+ raise Nomade::DispatchMissingMetaData.new("Dispatch job expects metakey #{required_key} but it was not set")
325
+ end
326
+ end
327
+ end
328
+
329
+ allowed_meta_tags = [meta_required, meta_optional].flatten.uniq
330
+ @logger.info "Dispatch job allows the following metakeys: #{allowed_meta_tags.join(", ")}"
331
+ if payload_metadata
332
+ payload_metadata.keys.each do |metadata_key|
333
+ unless allowed_meta_tags.include?(metadata_key)
334
+ raise Nomade::DispatchUnknownMetaData.new("Dispatch job does not allow #{metadata_key} to be set!")
335
+ end
336
+ end
337
+ end
338
+
339
+ case payload
340
+ when "optional", ""
341
+ @logger.info "Expectation for Payload is: optional"
342
+ when "required"
343
+ @logger.info "Expectation for Payload is: required"
344
+
345
+ unless payload_data
346
+ raise Nomade::DispatchMissingPayload.new("Dispatch job expects payload_data, but we don't supply any!")
347
+ end
348
+ when "forbidden"
349
+ @logger.info "Expectation for Payload is: forbidden"
350
+
351
+ if payload_data
352
+ raise Nomade::DispatchPayloadNotAllowed.new("Dispatch job do not allow payload_data!")
353
+ end
354
+ else
355
+ raise Nomade::DispatchPayloadUnknown.new("Invalid value for [\"ParameterizedJob\"][\"Payload\"] = #{payload}")
356
+ end
357
+
358
+ @logger.info "Checking cluster for connectivity and capacity.."
359
+ plan_data = @http.plan_job(@nomad_job)
360
+
361
+ dispatch_job = @http.dispatch_job(@nomad_job, payload_data: payload_data, payload_metadata: payload_metadata)
362
+ @evaluation_id = dispatch_job["EvalID"]
363
+
364
+ @logger.info "Waiting until allocations are no longer pending"
365
+ allocations = []
366
+ until !allocations.empty? && allocations.all?{|a| a["ClientStatus"] != "pending"}
367
+ @logger.info "."
368
+ sleep(2)
369
+ allocations = @http.allocations_from_evaluation_request(@evaluation_id)
370
+ end
371
+
372
+ batch_deploy
373
+ end
374
+
190
375
  def service_deploy
191
376
  @logger.info "Waiting until tasks are placed"
192
377
  deploy_timeout = Time.now.utc + @timeout
@@ -16,4 +16,19 @@ module Nomade
16
16
 
17
17
  class UnsupportedDeploymentMode < StandardError; end
18
18
  class FailedTaskGroupPlan < StandardError; end
19
+
20
+ class DispatchWrongJobType < StandardError; end
21
+ class DispatchNotParamaterized < StandardError; end
22
+
23
+ class DispatchMetaDataFormattingError < StandardError; end
24
+ class DispatchMissingMetaData < StandardError; end
25
+ class DispatchUnknownMetaData < StandardError; end
26
+
27
+ class DispatchMissingPayload < StandardError; end
28
+ class DispatchPayloadNotAllowed < StandardError; end
29
+ class DispatchPayloadUnknown < StandardError; end
30
+
31
+ class HttpConnectionError < StandardError; end
32
+ class HttpBadResponse < StandardError; end
33
+ class HttpBadContentType < StandardError; end
19
34
  end
data/lib/nomade/hooks.rb CHANGED
@@ -3,5 +3,9 @@ module Nomade
3
3
  DEPLOY_RUNNING = Class.new
4
4
  DEPLOY_FINISHED = Class.new
5
5
  DEPLOY_FAILED = Class.new
6
+
7
+ DISPATCH_RUNNING = Class.new
8
+ DISPATCH_FINISHED = Class.new
9
+ DISPATCH_FAILED = Class.new
6
10
  end
7
11
  end
data/lib/nomade/http.rb CHANGED
@@ -154,12 +154,34 @@ module Nomade
154
154
  raise
155
155
  end
156
156
 
157
+ def dispatch_job(nomad_job, payload_data: nil, payload_metadata: nil)
158
+ if payload_metadata.class != Hash
159
+ raise DispatchMetaDataFormattingError.new("Expected #{payload_metadata} to be a Hash, but received #{payload_metadata.class}")
160
+ end
161
+
162
+ req_body = JSON.generate({
163
+ "Payload": payload_data,
164
+ "Meta": payload_metadata,
165
+ }.delete_if { |k, v| v.nil? })
166
+
167
+ res_body = _request(:post, "/v1/job/#{nomad_job.job_name}/dispatch", body: req_body)
168
+ JSON.parse(res_body)
169
+ rescue StandardError => e
170
+ Nomade.logger.fatal "HTTP Request failed (#{e.message})"
171
+ raise
172
+ end
173
+
157
174
  private
158
175
 
159
176
  def _request(request_type, path, body: nil, total_retries: 0, expected_content_type: "application/json")
160
177
  uri = URI("#{@nomad_endpoint}#{path}")
161
178
 
162
179
  http = Net::HTTP.new(uri.host, uri.port)
180
+ http.open_timeout = 10
181
+ http.read_timeout = 10
182
+ http.write_timeout = 10
183
+ http.ssl_timeout = 10
184
+
163
185
  if @nomad_endpoint.include?("https://")
164
186
  http.use_ssl = true
165
187
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
@@ -181,24 +203,27 @@ module Nomade
181
203
  res = begin
182
204
  retries ||= 0
183
205
  http.request(req)
184
- rescue Timeout::Error, Errno::ETIMEDOUT, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, SocketError
206
+ rescue Timeout::Error, Errno::ETIMEDOUT, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, SocketError => e
185
207
  if retries < total_retries
186
208
  retries += 1
187
209
  sleep 1
188
210
  retry
189
211
  else
190
- raise
212
+ raise HttpConnectionError.new("#{e.class.to_s} - #{e.message}")
191
213
  end
192
214
  end
193
215
 
194
- raise if res.code != "200"
216
+ if res.code != "200"
217
+ raise HttpBadResponse.new("Bad response (not 200) but #{res.code}: #{res.body}")
218
+ end
219
+
195
220
  if res.content_type != expected_content_type
196
221
  # Sometimes the log endpoint doesn't set content_type on no content
197
222
  # https://github.com/hashicorp/nomad/issues/7264
198
223
  if res.content_type == nil && expected_content_type == "text/plain"
199
224
  # don't raise
200
225
  else
201
- raise
226
+ raise HttpBadContentType.new("Expected #{expected_content_type} but got #{res.content_type}")
202
227
  end
203
228
  end
204
229
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nomade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Grubbe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-25 00:00:00.000000000 Z
11
+ date: 2020-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yell