nomade 0.1.3 → 0.1.4

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