Skip to content

Commit 47446d3

Browse files
authored
Scale disk, memory, and log rate with deployments (#4264)
* also dont set web_instances unless explicitly set
1 parent f629077 commit 47446d3

File tree

15 files changed

+742
-135
lines changed

15 files changed

+742
-135
lines changed

app/actions/deployment_create.rb

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ def create(app:, user_audit_info:, message:)
1313
DeploymentModel.db.transaction do
1414
app.lock!
1515

16+
# Stopped apps will have quota validated since we scale their process up immediately later
17+
validate_quota!(message, app) unless app.stopped?
18+
1619
message.strategy ||= DeploymentModel::ROLLING_STRATEGY
1720

1821
target_state = DeploymentTargetState.new(app, message)
@@ -29,45 +32,45 @@ def create(app:, user_audit_info:, message:)
2932

3033
previous_deployment = DeploymentModel.find(app: app, status_value: DeploymentModel::ACTIVE_STATUS_VALUE)
3134

35+
deployment = create_deployment(
36+
app,
37+
message,
38+
previous_deployment,
39+
previous_droplet,
40+
revision,
41+
target_state,
42+
user_audit_info
43+
)
44+
3245
if app.stopped?
33-
return deployment_for_stopped_app(
34-
app,
35-
message,
36-
previous_deployment,
37-
previous_droplet,
38-
revision,
39-
target_state,
40-
user_audit_info
41-
)
46+
process = app.newest_web_process
47+
else
48+
process_instances = starting_process_instances(deployment, desired_instances(app.oldest_web_process, previous_deployment))
49+
process = create_deployment_process(app, deployment.guid, revision, process_instances)
4250
end
4351

44-
desired_instances = desired_instances(app.oldest_web_process, previous_deployment)
45-
46-
validate_quota!(message, app)
47-
48-
deployment = DeploymentModel.create(
49-
app: app,
50-
state: starting_state(message),
51-
status_value: DeploymentModel::ACTIVE_STATUS_VALUE,
52-
status_reason: DeploymentModel::DEPLOYING_STATUS_REASON,
53-
droplet: target_state.droplet,
54-
previous_droplet: previous_droplet,
55-
original_web_process_instance_count: desired_instances,
56-
revision_guid: revision&.guid,
57-
revision_version: revision&.version,
58-
strategy: message.strategy,
59-
max_in_flight: message.max_in_flight,
60-
canary_steps: message.options&.dig(:canary, :steps),
61-
web_instances: message.web_instances || desired_instances
62-
)
52+
process.memory = message.memory_in_mb if message.memory_in_mb
53+
process.disk_quota = message.disk_in_mb if message.disk_in_mb
54+
process.log_rate_limit = message.log_rate_limit_in_bytes_per_second if message.log_rate_limit_in_bytes_per_second
6355

64-
MetadataUpdate.update(deployment, message)
56+
if app.stopped?
57+
process.instances = message.web_instances if message.web_instances
6558

66-
supersede_deployment(previous_deployment)
59+
process.save_changes
60+
61+
# Do not create a revision here because AppStart will not handle the rollback case
62+
AppStart.start(app: app, user_audit_info: user_audit_info, create_revision: false)
63+
deployment.update(state: DeploymentModel::DEPLOYED_STATE,
64+
status_value: DeploymentModel::FINALIZED_STATUS_VALUE,
65+
status_reason: DeploymentModel::DEPLOYED_STATUS_REASON)
66+
record_audit_event(deployment, target_state.droplet, user_audit_info, message)
67+
return deployment
68+
end
6769

68-
process_instances = starting_process_instances(deployment, desired_instances)
70+
process.save
71+
72+
supersede_deployment(previous_deployment)
6973

70-
process = create_deployment_process(app, deployment.guid, revision, process_instances)
7174
# Need to transition from STOPPED to STARTED to engage the ProcessObserver to desire the LRP.
7275
# It'd be better to do this via Diego::Runner.new(process, config).start,
7376
# but it is nontrivial to get that working in test.
@@ -104,7 +107,6 @@ def clone_existing_web_process(app, revision, process_instances)
104107
else
105108
web_process.command
106109
end
107-
108110
ProcessModel.create(
109111
app: app,
110112
type: ProcessTypes::WEB,
@@ -167,43 +169,42 @@ def record_audit_event(deployment, droplet, user_audit_info, message)
167169
private
168170

169171
def validate_quota!(message, app)
170-
return if message.web_instances.blank?
172+
return if message.web_instances.blank? && message.memory_in_mb.blank? && message.log_rate_limit_in_bytes_per_second.blank?
171173

172174
current_web_process = app.newest_web_process
173-
current_web_process.instances = message.web_instances
174-
175+
current_web_process.instances = message.web_instances if message.web_instances
176+
current_web_process.memory = message.memory_in_mb if message.memory_in_mb
177+
current_web_process.disk_quota = message.disk_in_mb if message.disk_in_mb
178+
current_web_process.log_rate_limit = message.log_rate_limit_in_bytes_per_second if message.log_rate_limit_in_bytes_per_second
179+
# Quotas wont get checked unless the process is started
180+
current_web_process.state = ProcessModel::STARTED
175181
current_web_process.validate
176182

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

179185
current_web_process.reload
180186
end
181187

182-
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
184-
# Do not create a revision here because AppStart will not handle the rollback case
185-
AppStart.start(app: app, user_audit_info: user_audit_info, create_revision: false)
186-
188+
def create_deployment(app, message, previous_deployment, previous_droplet, revision, target_state, _user_audit_info)
187189
deployment = DeploymentModel.create(
188190
app: app,
189-
state: DeploymentModel::DEPLOYED_STATE,
190-
status_value: DeploymentModel::FINALIZED_STATUS_VALUE,
191-
status_reason: DeploymentModel::DEPLOYED_STATUS_REASON,
191+
state: starting_state(message),
192+
status_value: DeploymentModel::ACTIVE_STATUS_VALUE,
193+
status_reason: DeploymentModel::DEPLOYING_STATUS_REASON,
192194
droplet: target_state.droplet,
193195
previous_droplet: previous_droplet,
194196
original_web_process_instance_count: desired_instances(app.oldest_web_process, previous_deployment),
195197
revision_guid: revision&.guid,
196198
revision_version: revision&.version,
197199
strategy: message.strategy,
198200
max_in_flight: message.max_in_flight,
201+
memory_in_mb: message.memory_in_mb,
202+
disk_in_mb: message.disk_in_mb,
203+
log_rate_limit_in_bytes_per_second: message.log_rate_limit_in_bytes_per_second,
199204
canary_steps: message.options&.dig(:canary, :steps),
200-
web_instances: message.web_instances || desired_instances(app.oldest_web_process, previous_deployment)
205+
web_instances: message.web_instances
201206
)
202-
203207
MetadataUpdate.update(deployment, message)
204-
205-
record_audit_event(deployment, target_state.droplet, user_audit_info, message)
206-
207208
deployment
208209
end
209210

app/messages/deployment_create_message.rb

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'messages/metadata_base_message'
2+
require 'messages/process_scale_message'
23

34
module VCAP::CloudController
45
class DeploymentCreateMessage < MetadataBaseMessage
@@ -14,6 +15,9 @@ class DeploymentCreateMessage < MetadataBaseMessage
1415
canary
1516
max_in_flight
1617
web_instances
18+
memory_in_mb
19+
disk_in_mb
20+
log_rate_limit_in_bytes_per_second
1721
].freeze
1822

1923
ALLOWED_STEP_KEYS = [
@@ -48,6 +52,18 @@ def web_instances
4852
options&.dig(:web_instances)
4953
end
5054

55+
def memory_in_mb
56+
options&.dig(:memory_in_mb)
57+
end
58+
59+
def disk_in_mb
60+
options&.dig(:disk_in_mb)
61+
end
62+
63+
def log_rate_limit_in_bytes_per_second
64+
options&.dig(:log_rate_limit_in_bytes_per_second)
65+
end
66+
5167
private
5268

5369
def mutually_exclusive_droplet_sources
@@ -66,12 +82,31 @@ def validate_options
6682

6783
disallowed_keys = options.keys - ALLOWED_OPTION_KEYS
6884
errors.add(:options, "has unsupported key(s): #{disallowed_keys.join(', ')}") if disallowed_keys.present?
69-
70-
validate_web_instances if options[:web_instances]
85+
validate_scaling_options
7186
validate_max_in_flight if options[:max_in_flight]
7287
validate_canary if options[:canary]
7388
end
7489

90+
def validate_scaling_options
91+
scaling_options = {
92+
instances: options[:web_instances],
93+
memory_in_mb: options[:memory_in_mb],
94+
disk_in_mb: options[:disk_in_mb],
95+
log_rate_limit_in_bytes_per_second: options[:log_rate_limit_in_bytes_per_second]
96+
}
97+
98+
message = ProcessScaleMessage.new(scaling_options)
99+
message.valid?
100+
if message.errors[:instances].present?
101+
message.errors.select { |e| e.attribute == :instances }.each do |error|
102+
errors.import(error, { attribute: :web_instances })
103+
end
104+
message.errors.delete(:instances)
105+
end
106+
107+
errors.merge!(message.errors)
108+
end
109+
75110
def validate_max_in_flight
76111
max_in_flight = options[:max_in_flight]
77112

app/models/runtime/constraints/max_log_rate_limit_policy.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class AppMaxLogRateLimitPolicy < BaseMaxLogRateLimitPolicy
5050

5151
def additional_checks
5252
resource.started? &&
53-
(resource.column_changed?(:state) || resource.column_changed?(:instances))
53+
(resource.column_changed?(:state) || resource.column_changed?(:instances) || resource.column_changed?(:log_rate_limit))
5454
end
5555

5656
def requested_log_rate_limit

app/models/runtime/deployment_model.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,6 @@ def continuable?
114114
end
115115

116116
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
121117
web_instances || original_web_process_instance_count
122118
end
123119

app/presenters/v3/deployment_presenter.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,12 @@ def new_processes
5757

5858
def options(deployment)
5959
options = {
60-
max_in_flight: deployment.max_in_flight,
61-
web_instances: deployment.desired_web_instances
60+
max_in_flight: deployment.max_in_flight
6261
}
62+
options[:web_instances] = deployment.web_instances if deployment.web_instances
63+
options[:memory_in_mb] = deployment.memory_in_mb if deployment.memory_in_mb
64+
options[:disk_in_mb] = deployment.disk_in_mb if deployment.disk_in_mb
65+
options[:log_rate_limit_in_bytes_per_second] = deployment.log_rate_limit_in_bytes_per_second if deployment.log_rate_limit_in_bytes_per_second
6366

6467
if deployment.strategy == VCAP::CloudController::DeploymentModel::CANARY_STRATEGY && deployment.canary_steps
6568
options[:canary] = {

config/cloud_controller.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ readiness_port:
1111

1212
external_protocol: http
1313
external_domain: api2.vcap.me
14-
temporary_disable_deployments: true
14+
temporary_disable_deployments: false
1515
deployment_updater:
1616
update_frequency_in_seconds: 30
1717
lock_key: 'cf-deployment-updater'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Sequel.migration do
2+
up do
3+
alter_table(:deployments) do
4+
add_column :memory_in_mb, :integer, null: true
5+
add_column :disk_in_mb, :integer, null: true
6+
add_column :log_rate_limit_in_bytes_per_second, :integer, null: true
7+
end
8+
end
9+
down do
10+
alter_table(:deployments) do
11+
drop_column :memory_in_mb
12+
drop_column :disk_in_mb
13+
drop_column :log_rate_limit_in_bytes_per_second
14+
end
15+
end
16+
end

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
"options" : {
2121
"max_in_flight": 3,
2222
"web_instances": 5,
23+
"memory_in_mb": 1024,
24+
"disk_in_mb": 1024,
25+
"log_rate_limit_in_bytes_per_second": -1,
2326
"canary": {
2427
"steps": [
2528
{

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ Name | Type | Description | Default
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
8181
**options.web_instances** | _integer_ | The number of web instances the deployment will scale to | The current web process's instance count
82+
**options.memory_in_mb** | _integer_ | The amount of memory in megabytes to allocate per web process instance. If `null`, the amount allocated will be taken from the previous web process. | `null`
83+
**options.disk_in_mb** | _integer_ | The amount of disk in megabytes to allocate per web process instance. If `null`, the amount allocated will be taken from the previous web process. | `null`
84+
**options.log_rate_limit_in_bytes_per_second** | _integer_ | Log rate limit in bytes per second to allocate per web process instance. If `null`, the amount allocated will be taken from the previous web process. | `null`
8285
**options.canary.steps** | _array of [canary step objects](#canary-steps-object)_ | An array of canary steps to use for the deployment
8386
**metadata.labels** | [_label object_](#labels) | Labels applied to the deployment
8487
**metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the deployment

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Name | Type | Description
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
2323
**options.web_instances** | _integer_ | The number of web instances the deployment will scale to
24+
**options.memory_in_mb** | _integer_ | The amount of memory in megabytes to allocate per web process instance. If `null`, the amount allocated will be taken from the previous web process.
25+
**options.disk_in_mb** | _integer_ | The amount of disk in megabytes to allocate per web process instance. If `null`, the amount allocated will be taken from the previous web process.
26+
**options.log_rate_limit_in_bytes_per_second** | _integer_ | Log rate limit in bytes per second to allocate per web process instance. If `null`, the amount allocated will be taken from the previous web process.
2427
**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)
2528
**droplet.guid** | _string_ | The droplet guid that the deployment is transitioning the app to
2629
**previous_droplet.guid** | _string_ | The app's [current droplet guid](#get-current-droplet-association-for-an-app) before the deployment was created

0 commit comments

Comments
 (0)