Skip to content

Commit 503e65e

Browse files
Samzesethboyles
authored andcommitted
wip more validations
1 parent 97dc432 commit 503e65e

File tree

3 files changed

+61
-66
lines changed

3 files changed

+61
-66
lines changed

app/messages/deployment_create_message.rb

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,21 @@ class DeploymentCreateMessage < MetadataBaseMessage
1010
options
1111
]
1212

13+
ALLOWED_OPTION_KEYS = %i[
14+
canary
15+
max_in_flight
16+
].freeze
17+
1318
ALLOWED_STEP_KEYS = [
1419
:instance_weight
15-
]
20+
].freeze
1621

1722
validates_with NoAdditionalKeysValidator
1823
validates :strategy,
1924
inclusion: { in: %w[rolling canary], message: "'%<value>s' is not a supported deployment strategy" },
2025
allow_nil: true
2126
validate :mutually_exclusive_droplet_sources
2227

23-
# validates :options,
24-
# allow_nil: true,
25-
# hash: true
26-
2728
validate :validate_options
2829

2930
def app_guid
@@ -51,13 +52,16 @@ def mutually_exclusive_droplet_sources
5152
end
5253

5354
def validate_options
54-
return unless options.present?
55+
return if options.blank?
5556

5657
unless options.is_a?(Hash)
5758
errors.add(:options, 'must be an object')
5859
return
5960
end
6061

62+
disallowed_keys = options.keys - ALLOWED_OPTION_KEYS
63+
errors.add(:options, "has unsupported key(s): #{disallowed_keys.join(', ')}") if disallowed_keys.present?
64+
6165
validate_max_in_flight if options[:max_in_flight]
6266
validate_canary if options[:canary]
6367
end
@@ -98,6 +102,12 @@ def validate_steps
98102
errors.add(:'options.canary.steps', "has unsupported key(s): #{disallowed_keys.join(', ')}") if disallowed_keys.present?
99103
end
100104

105+
validate_step_instance_weights
106+
end
107+
108+
def validate_step_instance_weights
109+
steps = options[:canary][:steps]
110+
101111
errors.add(:'options.canary.steps', 'missing key: "instance_weight"') if steps.any? { |step| !step.key?(:instance_weight) }
102112

103113
if steps.any? { |step| !step[:instance_weight].is_a?(Integer) }
@@ -107,7 +117,7 @@ def validate_steps
107117

108118
errors.add(:'options.canary.steps.instance_weight', 'must be an Integer between 1-100 (inclusive)') if steps.any? { |step| (1..100).exclude?(step[:instance_weight]) }
109119

110-
weights = steps.map { |step| step[:instance_weight] }
120+
weights = steps.pluck(:instance_weight)
111121
return unless weights.sort != weights
112122

113123
errors.add(:'options.canary.steps.instance_weight', 'must be sorted in ascending order')

app/messages/validators.rb

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -257,46 +257,6 @@ def validate(record)
257257
end
258258
end
259259

260-
class DeploymentOptionsValidator < ActiveModel::Validator
261-
def validate(record)
262-
options = record.options
263-
return unless options.present?
264-
265-
unless options.is_a?(Hash)
266-
record.errors.add(:options, 'must be an object')
267-
return
268-
end
269-
270-
max_in_flight = options[:max_in_flight]
271-
272-
# TODO: figure out if we should allow max_in_flight set to nil explicitly
273-
record.errors.add(:max_in_flight, 'must be an integer greater than 0') if options.key?(:max_in_flight) && (!max_in_flight.is_a?(Integer) || max_in_flight < 1)
274-
275-
canary_options = options[:canary]
276-
return if canary_options.nil?
277-
278-
unless canary_options.is_a?(Hash)
279-
record.errors.add(:'options.canary', 'must be an object')
280-
return
281-
end
282-
283-
# binding.pry
284-
285-
strategy = record.strategy
286-
287-
if canary_options && strategy != 'canary'
288-
record.errors.add(:'options.canary', 'are only valid for Canary deployments')
289-
return
290-
end
291-
292-
steps = canary_options[:steps]
293-
294-
return unless steps && (!steps.is_a?(Array) || steps.any? { |step| !step.is_a?(Hash) })
295-
296-
record.errors.add(:'options.canary.steps', 'must be an array of objects')
297-
end
298-
end
299-
300260
class ToOneRelationshipValidator < ActiveModel::EachValidator
301261
def validate_each(record, attribute, relationship)
302262
if has_correct_structure?(relationship)

spec/unit/messages/deployment_create_message_spec.rb

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ module VCAP::CloudController
167167
expect(message.errors[:'options.canary']).to include('are only valid for Canary deployments')
168168
end
169169

170+
it 'errors when there is an unknown option' do
171+
body['options'] = { foo: 'bar', baz: 'boo' }
172+
message = DeploymentCreateMessage.new(body)
173+
expect(message).not_to be_valid
174+
expect(message.errors[:options]).to include('has unsupported key(s): foo, baz')
175+
end
176+
170177
context 'steps' do
171178
it 'errors when is not an array' do
172179
body['options'] = { canary: { steps: 'foo' } }
@@ -175,7 +182,7 @@ module VCAP::CloudController
175182
expect(message.errors[:'options.canary.steps']).to include('must be an array of objects')
176183
end
177184

178-
it 'is valid when is an array' do
185+
it 'is valid when is an empty array' do
179186
body['options'] = { canary: { steps: [] } }
180187
message = DeploymentCreateMessage.new(body)
181188
expect(message).to be_valid
@@ -187,12 +194,6 @@ module VCAP::CloudController
187194
expect(message).to be_valid
188195
end
189196

190-
it 'is valid if instance_weights are Integers between 1-100 in ascending order' do
191-
body['options'] = { canary: { steps: [{ instance_weight: 1 }, { instance_weight: 2 }, { instance_weight: 50 }, { instance_weight: 99 }, { instance_weight: 100 }] } }
192-
message = DeploymentCreateMessage.new(body)
193-
expect(message).to be_valid
194-
end
195-
196197
it 'errors if not an array of objects' do
197198
body['options'] = { canary: { steps: [{ instance_weight: 1 }, 'foo'] } }
198199
message = DeploymentCreateMessage.new(body)
@@ -209,20 +210,53 @@ module VCAP::CloudController
209210
end
210211

211212
context 'instance_weights' do
213+
it 'is valid if instance_weights are Integers between 1-100 in ascending order' do
214+
body['options'] = { canary: { steps: [{ instance_weight: 1 }, { instance_weight: 2 }, { instance_weight: 50 }, { instance_weight: 99 }, { instance_weight: 100 }] } }
215+
message = DeploymentCreateMessage.new(body)
216+
expect(message).to be_valid
217+
end
218+
219+
it 'is valid if there are duplicate instance_weights' do
220+
body['options'] = { canary: { steps: [{ instance_weight: 10 }, { instance_weight: 10 }, { instance_weight: 50 }, { instance_weight: 50 }] } }
221+
message = DeploymentCreateMessage.new(body)
222+
expect(message).to be_valid
223+
end
224+
212225
it 'errors if steps are missing instance_weight' do
213226
body['options'] = { canary: { steps: [{ instance_weight: 1 }, { foo: 'bar' }] } }
214227
message = DeploymentCreateMessage.new(body)
215228
expect(message).not_to be_valid
216229
expect(message.errors[:'options.canary.steps']).to include('missing key: "instance_weight"')
217230
end
218231

219-
it 'errors if any instance_weight is less than or equal to 0' do
232+
it 'errors if steps are missing instance_weight with empty hash' do
233+
body['options'] = { canary: { steps: [{ instance_weight: 1 }, {}] } }
234+
message = DeploymentCreateMessage.new(body)
235+
expect(message).not_to be_valid
236+
expect(message.errors[:'options.canary.steps']).to include('missing key: "instance_weight"')
237+
end
238+
239+
it 'errors if any instance_weight is not an Integer' do
240+
body['options'] = { canary: { steps: [{ instance_weight: 'foo' }] } }
241+
message = DeploymentCreateMessage.new(body)
242+
expect(message).not_to be_valid
243+
expect(message.errors[:'options.canary.steps.instance_weight']).to include('must be an Integer between 1-100 (inclusive)')
244+
end
245+
246+
it 'errors if any instance_weight is equal to 0' do
220247
body['options'] = { canary: { steps: [{ instance_weight: 0 }, { instance_weight: 50 }] } }
221248
message = DeploymentCreateMessage.new(body)
222249
expect(message).not_to be_valid
223250
expect(message.errors[:'options.canary.steps.instance_weight']).to include('must be an Integer between 1-100 (inclusive)')
224251
end
225252

253+
it 'errors if any instance_weight is less than 0' do
254+
body['options'] = { canary: { steps: [{ instance_weight: -5 }, { instance_weight: 50 }] } }
255+
message = DeploymentCreateMessage.new(body)
256+
expect(message).not_to be_valid
257+
expect(message.errors[:'options.canary.steps.instance_weight']).to include('must be an Integer between 1-100 (inclusive)')
258+
end
259+
226260
it 'errors if any instance_weight is greater than 100' do
227261
body['options'] = { canary: { steps: [{ instance_weight: 50 }, { instance_weight: 101 }] } }
228262
message = DeploymentCreateMessage.new(body)
@@ -237,8 +271,7 @@ module VCAP::CloudController
237271
expect(message.errors[:'options.canary.steps.instance_weight']).to include('must be sorted in ascending order')
238272
end
239273

240-
it 'errors if any instance_weights are not an Integer' do
241-
# TODO: should we coerce values like 25.0 to 25? only error on 25.1 etc?
274+
it 'errors if any instance_weights are not a non-integer numeric' do
242275
body['options'] = { canary: { steps: [{ instance_weight: 2 }, { instance_weight: 25.0 }] } }
243276
message = DeploymentCreateMessage.new(body)
244277
expect(message).not_to be_valid
@@ -300,7 +333,7 @@ module VCAP::CloudController
300333

301334
context 'when options is specified, but not max_in_flight' do
302335
before do
303-
body['options'] = { other_key: 'other_value' }
336+
body['options'] = { canary: nil }
304337
end
305338

306339
it 'returns the default value of 1' do
@@ -334,13 +367,5 @@ module VCAP::CloudController
334367
end
335368
end
336369
end
337-
338-
context 'validations' do
339-
context 'when one instance_weight is below 0'
340-
context 'when one instance_weight is above 100'
341-
context 'when an instance weight is a decimal or non-integer'
342-
context 'when canary steps is an empty array'
343-
context 'when canary steps are non-increasing (e.g. 1,50,20)'
344-
end
345370
end
346371
end

0 commit comments

Comments
 (0)