Skip to content

Commit 544cac6

Browse files
authored
Stack management (#4712)
* Stack Management - DB Migration Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Stack Management - Model update and Unit Test Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Expose stack state through V3 API endpoints only **V2 API endpoints must not expose or accept state parameter.** Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Add Stack States in API documentation Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Additional validation and tests for v3/stacks API Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Standalone Stack State Validator - State Logic class Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Stack state validation and integration into app creation and build workflows. Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * V2 API stack validation and tests Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Add deprecation warning in build create Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Update stck state validation error and warning messages Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Stack state validation when creating and updating apps via V2 and V3 APIs Sets warnings in the response hash when there are warnings Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Removes stack_warnings from the models. Actions own their warnings and Controllers set warnings → headers Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Describe stack states in API doc Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Adds warning when changing default stack state Polishing based on review comments Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> * Added warning for restaging app on restricted stack Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com> --------- Signed-off-by: Rashed Kamal <rashed.kamal@broadcom.com>
1 parent 8dd861c commit 544cac6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1996
-19
lines changed

app/actions/app_create.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class AppCreate
99

1010
class InvalidApp < StandardError; end
1111

12+
attr_reader :warnings
13+
1214
def initialize(user_audit_info)
1315
@user_audit_info = user_audit_info
1416
@logger = Steno.logger('cc.action.app_create')
@@ -24,6 +26,7 @@ def create(message, lifecycle)
2426
)
2527

2628
lifecycle.create_lifecycle_data_model(app)
29+
@warnings = validate_stack_state(app, lifecycle)
2730
validate_buildpacks_are_ready(app)
2831

2932
MetadataUpdate.update(app, message)
@@ -47,6 +50,8 @@ def create(message, lifecycle)
4750
rescue Sequel::ValidationFailed => e
4851
v3_api_error!(:UniquenessError, e.message) if e.errors.on(%i[space_guid name])
4952

53+
raise InvalidApp.new(e.message)
54+
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
5055
raise InvalidApp.new(e.message)
5156
end
5257

@@ -74,5 +79,17 @@ def validate_buildpacks_are_ready(app)
7479
end
7580
end
7681
end
82+
83+
def validate_stack_state(app, lifecycle)
84+
return [] if lifecycle.type == Lifecycles::DOCKER
85+
86+
stack_name = app.lifecycle_data.try(:stack)
87+
return [] unless stack_name
88+
89+
stack = Stack.find(name: stack_name)
90+
return [] unless stack
91+
92+
StackStateValidator.validate_for_new_app!(stack)
93+
end
7794
end
7895
end

app/actions/app_update.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class AppUpdate
77
class DropletNotFound < StandardError; end
88
class InvalidApp < StandardError; end
99

10+
attr_reader :warnings
11+
1012
def initialize(user_audit_info, manifest_triggered: false, runners: nil)
1113
@user_audit_info = user_audit_info
1214
@logger = Steno.logger('cc.action.app_update')
@@ -53,8 +55,12 @@ def update(app, message, lifecycle)
5355
end
5456
end
5557

58+
@warnings = validate_stack_state(app, message, lifecycle)
59+
5660
app
57-
rescue Sequel::ValidationFailed => e
61+
rescue Sequel::ValidationFailed,
62+
StackStateValidator::DisabledStackError,
63+
StackStateValidator::RestrictedStackError => e
5864
raise InvalidApp.new(e.message)
5965
end
6066

@@ -81,5 +87,19 @@ def validate_not_changing_lifecycle_type!(app, lifecycle)
8187
def existing_environment_variables_for(app)
8288
app.environment_variables.nil? ? {} : app.environment_variables.symbolize_keys
8389
end
90+
91+
def validate_stack_state(app, message, lifecycle)
92+
return [] if lifecycle.type == Lifecycles::DOCKER
93+
return [] unless message.requested?(:lifecycle) && message.buildpack_data.requested?(:stack)
94+
95+
stack = Stack.find(name: message.buildpack_data.stack)
96+
return [] unless stack
97+
98+
if app.builds_dataset.count.zero?
99+
StackStateValidator.validate_for_new_app!(stack)
100+
else
101+
StackStateValidator.validate_for_restaging!(stack)
102+
end
103+
end
84104
end
85105
end

app/actions/build_create.rb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class LogRateLimitOrgQuotaExceeded < BuildError
2525
class StagingInProgress < BuildError
2626
end
2727

28-
attr_reader :staging_response
28+
attr_reader :staging_response, :warnings
2929

3030
def initialize(user_audit_info: UserAuditInfo.from_context(SecurityContext),
3131
memory_limit_calculator: QuotaValidatingStagingMemoryCalculator.new,
@@ -41,6 +41,7 @@ def initialize(user_audit_info: UserAuditInfo.from_context(SecurityContext),
4141

4242
def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: false)
4343
logger.info("creating build for package #{package.guid}")
44+
@warnings = validate_stack_state!(lifecycle, package.app)
4445
staging_in_progress! if package.app.staging_in_progress?
4546
raise InvalidPackage.new('Cannot stage package whose state is not ready.') if package.state != PackageModel::READY_STATE
4647

@@ -179,5 +180,29 @@ def stagers
179180
def staging_in_progress!
180181
raise StagingInProgress
181182
end
183+
184+
def validate_stack_state!(lifecycle, app)
185+
return [] if lifecycle.type == Lifecycles::DOCKER
186+
187+
stack = Stack.find(name: lifecycle.staging_stack)
188+
return [] unless stack
189+
190+
warnings = if first_build_for_app?(app)
191+
StackStateValidator.validate_for_new_app!(stack)
192+
else
193+
StackStateValidator.validate_for_restaging!(stack)
194+
end
195+
warnings.each { |warning| logger.warn(warning) }
196+
warnings
197+
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
198+
raise CloudController::Errors::ApiError.new_from_details(
199+
'StackValidationFailed',
200+
e.message
201+
)
202+
end
203+
204+
def first_build_for_app?(app)
205+
app.builds_dataset.count.zero?
206+
end
182207
end
183208
end

app/actions/stack_create.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ def initialize(user_audit_info)
1212
def create(message)
1313
stack = VCAP::CloudController::Stack.create(
1414
name: message.name,
15-
description: message.description
15+
description: message.description,
16+
state: message.state
1617
)
1718

1819
MetadataUpdate.update(stack, message)

app/actions/stack_update.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ def initialize(user_audit_info)
1212

1313
def update(stack, message)
1414
stack.db.transaction do
15+
stack.update(state: message.state) if message.requested?(:state)
1516
MetadataUpdate.update(stack, message)
1617
Repositories::StackEventRepository.new.record_stack_update(stack, @user_audit_info, message.audit_hash)
1718
end
18-
@logger.info("Finished updating metadata on stack #{stack.guid}")
19+
@logger.info("Finished updating stack #{stack.guid}")
1920

2021
stack
2122
rescue Sequel::ValidationFailed => e

app/actions/v2/app_create.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module VCAP::CloudController
22
module V2
33
class AppCreate
4+
attr_reader :warnings
5+
46
def initialize(access_validator:)
57
@access_validator = access_validator
68
end
@@ -46,6 +48,8 @@ def create(request_attrs)
4648
@access_validator.validate_access(:create, process, request_attrs)
4749
end
4850

51+
@warnings = validate_stack_state(request_attrs)
52+
4953
process
5054
end
5155

@@ -106,6 +110,18 @@ def validate_package_exists!(process, request_attrs)
106110

107111
raise CloudController::Errors::ApiError.new_from_details('AppPackageInvalid', 'bits have not been uploaded')
108112
end
113+
114+
def validate_stack_state(request_attrs)
115+
return [] if request_attrs.key?('docker_image')
116+
117+
stack_name = get_stack_name(request_attrs['stack_guid'])
118+
stack = Stack.find(name: stack_name)
119+
return [] unless stack
120+
121+
StackStateValidator.validate_for_new_app!(stack)
122+
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
123+
raise CloudController::Errors::ApiError.new_from_details('StackValidationFailed', e.message)
124+
end
109125
end
110126
end
111127
end

app/actions/v2/app_stage.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
module VCAP::CloudController
22
module V2
33
class AppStage
4+
attr_reader :warnings
5+
46
def initialize(stagers:)
57
@stagers = stagers
8+
@warnings = []
69
end
710

811
def stage(process)
@@ -25,6 +28,9 @@ def stage(process)
2528
lifecycle: lifecycle,
2629
start_after_staging: true
2730
)
31+
32+
@warnings = build_creator.warnings || []
33+
2834
TelemetryLogger.v2_emit(
2935
'create-build',
3036
{

app/actions/v2/app_update.rb

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
module VCAP::CloudController
55
module V2
66
class AppUpdate
7+
attr_reader :warnings
8+
79
def initialize(access_validator:, stagers:)
810
@access_validator = access_validator
911
@stagers = stagers
12+
@warnings = []
1013
end
1114

1215
def update(app, process, request_attrs)
@@ -36,6 +39,7 @@ def update(app, process, request_attrs)
3639
prepare_to_stage(app) if staging_necessary?(process, request_attrs)
3740
end
3841

42+
validate_stack_state(app, request_attrs)
3943
stage(process) if staging_necessary?(process, request_attrs)
4044
end
4145

@@ -116,7 +120,9 @@ def prepare_to_stage(app)
116120
end
117121

118122
def stage(process)
119-
V2::AppStage.new(stagers: @stagers).stage(process)
123+
app_stage = V2::AppStage.new(stagers: @stagers)
124+
app_stage.stage(process)
125+
@warnings = app_stage.warnings
120126
end
121127

122128
def start_or_stop(app, request_attrs)
@@ -179,6 +185,23 @@ def staging_necessary?(process, request_attrs)
179185
def v2_api_staging_disabled?
180186
!!VCAP::CloudController::Config.config.get(:temporary_disable_v2_staging)
181187
end
188+
189+
def validate_stack_state(app, request_attrs)
190+
return unless request_attrs.key?('stack_guid')
191+
return if request_attrs.key?('docker_image')
192+
193+
stack = Stack.find(guid: request_attrs['stack_guid'])
194+
return unless stack
195+
196+
stack_warnings = if app.builds_dataset.count.zero?
197+
StackStateValidator.validate_for_new_app!(stack)
198+
else
199+
StackStateValidator.validate_for_restaging!(stack)
200+
end
201+
@warnings.concat(stack_warnings)
202+
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
203+
raise CloudController::Errors::ApiError.new_from_details('StackValidationFailed', e.message)
204+
end
182205
end
183206
end
184207
end

app/controllers/runtime/apps_controller.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ def update(guid)
294294

295295
updater = V2::AppUpdate.new(access_validator: self, stagers: @stagers)
296296
updater.update(app, process, request_attrs)
297+
updater.warnings.each { |warning| add_warning(warning) }
297298

298299
after_update(process)
299300

@@ -357,6 +358,8 @@ def create
357358
creator = V2::AppCreate.new(access_validator: self)
358359
process = creator.create(request_attrs)
359360

361+
creator.warnings&.each { |warning| add_warning(warning) }
362+
360363
@app_event_repository.record_app_create(
361364
process,
362365
process.space,

app/controllers/runtime/restages_controller.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ def restage(guid)
3535
process.app.update(droplet_guid: nil)
3636
AppStart.start_without_event(process.app, create_revision: false)
3737
end
38-
V2::AppStage.new(stagers: @stagers).stage(process)
38+
39+
app_stage = V2::AppStage.new(stagers: @stagers)
40+
app_stage.stage(process)
41+
app_stage.warnings.each { |warning| add_warning(warning) }
3942

4043
@app_event_repository.record_app_restage(process, UserAuditInfo.from_context(SecurityContext))
4144

0 commit comments

Comments
 (0)