Skip to content

Commit 9d4dee3

Browse files
authoredMar 17, 2025··
Allow scaling with Deployments directly (#4247)
1 parent d611317 commit 9d4dee3

File tree

15 files changed

+467
-28
lines changed

15 files changed

+467
-28
lines changed
 

‎app/actions/deployment_create.rb

+22-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ def create(app:, user_audit_info:, message:)
4343

4444
desired_instances = desired_instances(app.oldest_web_process, previous_deployment)
4545

46+
validate_quota!(message, app)
47+
4648
deployment = DeploymentModel.create(
4749
app: app,
4850
state: starting_state(message),
@@ -55,7 +57,8 @@ def create(app:, user_audit_info:, message:)
5557
revision_version: revision&.version,
5658
strategy: message.strategy,
5759
max_in_flight: message.max_in_flight,
58-
canary_steps: message.options&.dig(:canary, :steps)
60+
canary_steps: message.options&.dig(:canary, :steps),
61+
web_instances: message.web_instances || desired_instances
5962
)
6063

6164
MetadataUpdate.update(deployment, message)
@@ -163,7 +166,21 @@ def record_audit_event(deployment, droplet, user_audit_info, message)
163166

164167
private
165168

169+
def validate_quota!(message, app)
170+
return if message.web_instances.blank?
171+
172+
current_web_process = app.newest_web_process
173+
current_web_process.instances = message.web_instances
174+
175+
current_web_process.validate
176+
177+
raise Sequel::ValidationFailed.new(current_web_process) unless current_web_process.valid?
178+
179+
current_web_process.reload
180+
end
181+
166182
def deployment_for_stopped_app(app, message, previous_deployment, previous_droplet, revision, target_state, user_audit_info)
183+
app.newest_web_process.update(instances: message.web_instances) if message.web_instances
167184
# Do not create a revision here because AppStart will not handle the rollback case
168185
AppStart.start(app: app, user_audit_info: user_audit_info, create_revision: false)
169186

@@ -179,7 +196,8 @@ def deployment_for_stopped_app(app, message, previous_deployment, previous_dropl
179196
revision_version: revision&.version,
180197
strategy: message.strategy,
181198
max_in_flight: message.max_in_flight,
182-
canary_steps: message.options&.dig(:canary, :steps)
199+
canary_steps: message.options&.dig(:canary, :steps),
200+
web_instances: message.web_instances || desired_instances(app.oldest_web_process, previous_deployment)
183201
)
184202

185203
MetadataUpdate.update(deployment, message)
@@ -223,6 +241,8 @@ def starting_state(message)
223241
def starting_process_instances(deployment, desired_instances)
224242
starting_process_count = if deployment.strategy == DeploymentModel::CANARY_STRATEGY
225243
deployment.canary_step[:canary]
244+
elsif deployment.web_instances
245+
deployment.web_instances
226246
else
227247
desired_instances
228248
end

‎app/messages/deployment_create_message.rb

+14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class DeploymentCreateMessage < MetadataBaseMessage
1313
ALLOWED_OPTION_KEYS = %i[
1414
canary
1515
max_in_flight
16+
web_instances
1617
].freeze
1718

1819
ALLOWED_STEP_KEYS = [
@@ -43,6 +44,10 @@ def max_in_flight
4344
options&.dig(:max_in_flight) || 1
4445
end
4546

47+
def web_instances
48+
options&.dig(:web_instances)
49+
end
50+
4651
private
4752

4853
def mutually_exclusive_droplet_sources
@@ -62,6 +67,7 @@ def validate_options
6267
disallowed_keys = options.keys - ALLOWED_OPTION_KEYS
6368
errors.add(:options, "has unsupported key(s): #{disallowed_keys.join(', ')}") if disallowed_keys.present?
6469

70+
validate_web_instances if options[:web_instances]
6571
validate_max_in_flight if options[:max_in_flight]
6672
validate_canary if options[:canary]
6773
end
@@ -74,6 +80,14 @@ def validate_max_in_flight
7480
errors.add(:max_in_flight, 'must be an integer greater than 0')
7581
end
7682

83+
def validate_web_instances
84+
web_instances = options[:web_instances]
85+
86+
return unless !web_instances.is_a?(Integer) || web_instances < 1
87+
88+
errors.add(:web_instances, 'must be an integer greater than 0')
89+
end
90+
7791
def validate_canary
7892
canary_options = options[:canary]
7993
unless canary_options.is_a?(Hash)

‎app/models/runtime/deployment_model.rb

+16-2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ def continuable?
113113
state == DeploymentModel::PAUSED_STATE
114114
end
115115

116+
def desired_web_instances
117+
# It seems redundant to have method since web_instances defaults to original_web_process_instance_count,
118+
# (in deployment create action)
119+
# but this should handle deployments created on old API vms mid bosh deployment
120+
# we can probably clean this up in the future
121+
web_instances || original_web_process_instance_count
122+
end
123+
116124
def current_canary_instance_target
117125
canary_step[:canary]
118126
end
@@ -133,11 +141,17 @@ def canary_step_plan
133141

134142
return [{ canary: 1, original: original_web_process_instance_count }] if canary_steps.nil?
135143

144+
instances = if web_instances && web_instances < original_web_process_instance_count
145+
web_instances
146+
else
147+
original_web_process_instance_count
148+
end
149+
136150
canary_steps.map do |step|
137151
weight = step['instance_weight']
138-
target_canary = (original_web_process_instance_count * (weight.to_f / 100)).round.to_i
152+
target_canary = (instances * (weight.to_f / 100)).round.to_i
139153
target_canary = 1 if target_canary.zero?
140-
target_original = original_web_process_instance_count - target_canary + 1
154+
target_original = instances - target_canary + 1
141155
target_original = 0 if weight == 100
142156
{ canary: target_canary, original: target_original }
143157
end

‎app/presenters/v3/deployment_presenter.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ def new_processes
5757

5858
def options(deployment)
5959
options = {
60-
max_in_flight: deployment.max_in_flight
60+
max_in_flight: deployment.max_in_flight,
61+
web_instances: deployment.desired_web_instances
6162
}
6263

6364
if deployment.strategy == VCAP::CloudController::DeploymentModel::CANARY_STRATEGY && deployment.canary_steps
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Sequel.migration do
2+
up do
3+
alter_table(:deployments) do
4+
add_column :web_instances, :integer, null: true
5+
end
6+
end
7+
down do
8+
alter_table(:deployments) do
9+
drop_column :web_instances
10+
end
11+
end
12+
end

‎docs/v3/source/includes/api_resources/_deployments.erb

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"strategy": "canary",
2020
"options" : {
2121
"max_in_flight": 3,
22+
"web_instances": 5,
2223
"canary": {
2324
"steps": [
2425
{

‎docs/v3/source/includes/resources/deployments/_create.md.erb

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Name | Type | Description | Default
7878
**revision**<sup>[1]</sup> | _object_ | The [revision](#revisions) whose droplet to deploy for the app; this will update the app's [current droplet](#get-current-droplet-association-for-an-app) to this droplet |
7979
**strategy** | _string_ | The strategy to use for the deployment | `rolling`
8080
**options.max_in_flight** | _integer_ | The maximum number of new instances to deploy simultaneously | 1
81+
**options.web_instances** | _integer_ | The number of web instances the deployment will scale to | The current web process's instance count
8182
**options.canary.steps** | _array of [canary step objects](#canary-steps-object)_ | An array of canary steps to use for the deployment
8283
**metadata.labels** | [_label object_](#labels) | Labels applied to the deployment
8384
**metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the deployment

‎docs/v3/source/includes/resources/deployments/_object.md.erb

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Name | Type | Description
2020
**status.canary.steps.total** | _integer_ | The total number of canary steps. Only available for deployments with strategy 'canary'. (experimental)
2121
**strategy** | _string_ | Strategy used for the deployment; supported strategies are `rolling` and `canary` (experimental)
2222
**options.max_in_flight** | _integer_ | The maximum number of new instances to deploy simultaneously
23+
**options.web_instances** | _integer_ | The number of web instances the deployment will scale to
2324
**options.canary.steps** | _array of [canary step objects](#canary-steps-object)_ | Canary steps to use for the deployment. Only available for deployments with strategy 'canary'. (experimental)
2425
**droplet.guid** | _string_ | The droplet guid that the deployment is transitioning the app to
2526
**previous_droplet.guid** | _string_ | The app's [current droplet guid](#get-current-droplet-association-for-an-app) before the deployment was created

‎lib/cloud_controller/deployment_updater/updater.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def initialize(deployment, logger)
1414

1515
def scale
1616
with_error_logging('error-scaling-deployment') do
17-
finished = Actions::Scale.new(deployment, logger, deployment.original_web_process_instance_count).call
17+
finished = Actions::Scale.new(deployment, logger, deployment.desired_web_instances).call
1818
Actions::Finalize.new(deployment).call if finished
1919
logger.info("ran-deployment-update-for-#{deployment.guid}")
2020
end

0 commit comments

Comments
 (0)
Please sign in to comment.