Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions app/actions/deployment_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def create(app:, user_audit_info:, message:)

desired_instances = desired_instances(app.oldest_web_process, previous_deployment)

validate_quota!(message, app)

deployment = DeploymentModel.create(
app: app,
state: starting_state(message),
Expand All @@ -55,7 +57,8 @@ def create(app:, user_audit_info:, message:)
revision_version: revision&.version,
strategy: message.strategy,
max_in_flight: message.max_in_flight,
canary_steps: message.options&.dig(:canary, :steps)
canary_steps: message.options&.dig(:canary, :steps),
web_instances: message.web_instances || desired_instances
)

MetadataUpdate.update(deployment, message)
Expand Down Expand Up @@ -163,7 +166,21 @@ def record_audit_event(deployment, droplet, user_audit_info, message)

private

def validate_quota!(message, app)
return if message.web_instances.blank?

current_web_process = app.newest_web_process
current_web_process.instances = message.web_instances

current_web_process.validate

raise Sequel::ValidationFailed.new(current_web_process) unless current_web_process.valid?

current_web_process.reload
end

def deployment_for_stopped_app(app, message, previous_deployment, previous_droplet, revision, target_state, user_audit_info)
app.newest_web_process.update(instances: message.web_instances) if message.web_instances
# Do not create a revision here because AppStart will not handle the rollback case
AppStart.start(app: app, user_audit_info: user_audit_info, create_revision: false)

Expand All @@ -179,7 +196,8 @@ def deployment_for_stopped_app(app, message, previous_deployment, previous_dropl
revision_version: revision&.version,
strategy: message.strategy,
max_in_flight: message.max_in_flight,
canary_steps: message.options&.dig(:canary, :steps)
canary_steps: message.options&.dig(:canary, :steps),
web_instances: message.web_instances || desired_instances(app.oldest_web_process, previous_deployment)
)

MetadataUpdate.update(deployment, message)
Expand Down Expand Up @@ -223,6 +241,8 @@ def starting_state(message)
def starting_process_instances(deployment, desired_instances)
starting_process_count = if deployment.strategy == DeploymentModel::CANARY_STRATEGY
deployment.canary_step[:canary]
elsif deployment.web_instances
deployment.web_instances
else
desired_instances
end
Expand Down
14 changes: 14 additions & 0 deletions app/messages/deployment_create_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class DeploymentCreateMessage < MetadataBaseMessage
ALLOWED_OPTION_KEYS = %i[
canary
max_in_flight
web_instances
].freeze

ALLOWED_STEP_KEYS = [
Expand Down Expand Up @@ -43,6 +44,10 @@ def max_in_flight
options&.dig(:max_in_flight) || 1
end

def web_instances
options&.dig(:web_instances)
end

private

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

validate_web_instances if options[:web_instances]
validate_max_in_flight if options[:max_in_flight]
validate_canary if options[:canary]
end
Expand All @@ -74,6 +80,14 @@ def validate_max_in_flight
errors.add(:max_in_flight, 'must be an integer greater than 0')
end

def validate_web_instances
web_instances = options[:web_instances]

return unless !web_instances.is_a?(Integer) || web_instances < 1

errors.add(:web_instances, 'must be an integer greater than 0')
end

def validate_canary
canary_options = options[:canary]
unless canary_options.is_a?(Hash)
Expand Down
18 changes: 16 additions & 2 deletions app/models/runtime/deployment_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ def continuable?
state == DeploymentModel::PAUSED_STATE
end

def desired_web_instances
# It seems redundant to have method since web_instances defaults to original_web_process_instance_count,
# (in deployment create action)
# but this should handle deployments created on old API vms mid bosh deployment
# we can probably clean this up in the future
web_instances || original_web_process_instance_count
end

def current_canary_instance_target
canary_step[:canary]
end
Expand All @@ -133,11 +141,17 @@ def canary_step_plan

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

instances = if web_instances && web_instances < original_web_process_instance_count
web_instances
else
original_web_process_instance_count
end

canary_steps.map do |step|
weight = step['instance_weight']
target_canary = (original_web_process_instance_count * (weight.to_f / 100)).round.to_i
target_canary = (instances * (weight.to_f / 100)).round.to_i
target_canary = 1 if target_canary.zero?
target_original = original_web_process_instance_count - target_canary + 1
target_original = instances - target_canary + 1
target_original = 0 if weight == 100
{ canary: target_canary, original: target_original }
end
Expand Down
3 changes: 2 additions & 1 deletion app/presenters/v3/deployment_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ def new_processes

def options(deployment)
options = {
max_in_flight: deployment.max_in_flight
max_in_flight: deployment.max_in_flight,
web_instances: deployment.desired_web_instances
}

if deployment.strategy == VCAP::CloudController::DeploymentModel::CANARY_STRATEGY && deployment.canary_steps
Expand Down
12 changes: 12 additions & 0 deletions db/migrations/20250304215542_add_web_instances_to_deployments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Sequel.migration do
up do
alter_table(:deployments) do
add_column :web_instances, :integer, null: true
end
end
down do
alter_table(:deployments) do
drop_column :web_instances
end
end
end
1 change: 1 addition & 0 deletions docs/v3/source/includes/api_resources/_deployments.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"strategy": "canary",
"options" : {
"max_in_flight": 3,
"web_instances": 5,
"canary": {
"steps": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Name | Type | Description | Default
**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 |
**strategy** | _string_ | The strategy to use for the deployment | `rolling`
**options.max_in_flight** | _integer_ | The maximum number of new instances to deploy simultaneously | 1
**options.web_instances** | _integer_ | The number of web instances the deployment will scale to | The current web process's instance count
**options.canary.steps** | _array of [canary step objects](#canary-steps-object)_ | An array of canary steps to use for the deployment
**metadata.labels** | [_label object_](#labels) | Labels applied to the deployment
**metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the deployment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Name | Type | Description
**status.canary.steps.total** | _integer_ | The total number of canary steps. Only available for deployments with strategy 'canary'. (experimental)
**strategy** | _string_ | Strategy used for the deployment; supported strategies are `rolling` and `canary` (experimental)
**options.max_in_flight** | _integer_ | The maximum number of new instances to deploy simultaneously
**options.web_instances** | _integer_ | The number of web instances the deployment will scale to
**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)
**droplet.guid** | _string_ | The droplet guid that the deployment is transitioning the app to
**previous_droplet.guid** | _string_ | The app's [current droplet guid](#get-current-droplet-association-for-an-app) before the deployment was created
Expand Down
2 changes: 1 addition & 1 deletion lib/cloud_controller/deployment_updater/updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(deployment, logger)

def scale
with_error_logging('error-scaling-deployment') do
finished = Actions::Scale.new(deployment, logger, deployment.original_web_process_instance_count).call
finished = Actions::Scale.new(deployment, logger, deployment.desired_web_instances).call
Actions::Finalize.new(deployment).call if finished
logger.info("ran-deployment-update-for-#{deployment.guid}")
end
Expand Down
Loading