Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Readiness health checks #3351

Merged
merged 21 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9a3afa2
small docs change to trick github
ameowlia Jul 21, 2023
dc83ce3
Add readiness health check properties to app manifest
ameowlia Jul 19, 2023
faadcc4
Set readiness health check properties on process
ameowlia Jul 26, 2023
6aaff7b
correctly default to process for readiness_health_check_type
mariash Jul 26, 2023
be5b523
remove readiness_health_check_timeout
ameowlia Jul 26, 2023
456b232
cleanup: rename list of health check types
ameowlia Jul 27, 2023
3965310
Refactor: add base health check policy class
ameowlia Jul 28, 2023
86eb038
Set readiness health check properties on LRP
ameowlia Jul 27, 2023
c6cabd0
Refactor: app recipe builder
ameowlia Jul 28, 2023
01145c4
Fix: readiness hcs default to process not empty
ameowlia Jul 28, 2023
48db638
Add readiness health check properties to /v3/apps/:guid/manifest
mariash Jul 31, 2023
cd79600
Return diff for readiness check properties in /v3/spaces/:space_guid/…
mariash Aug 1, 2023
cda279e
Merge pull request #3368 from mariash/readiness-health-checks-story-4…
sethboyles Aug 3, 2023
5dff948
Readiness health checks story 5 processes (#3370)
mariash Aug 9, 2023
31a01d2
Rolling deploy respects readiness health check (#3373)
mariash Aug 10, 2023
5b95cf8
Readiness health checks story 7 respect defined interval (#3385)
mariash Aug 14, 2023
e4ee1ff
add need readiness check doc to index
ameowlia Aug 17, 2023
df1005c
make rubocop happy
moleske Aug 17, 2023
83c59cc
Disable rubocop cyclomaticcomplexity for some functions
moleske Aug 17, 2023
69abd47
Do not use text:true on readiness health check to process
moleske Aug 17, 2023
5aa46f2
Revert "small docs change to trick github"
moleske Aug 17, 2023
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
5 changes: 5 additions & 0 deletions app/actions/deployment_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ def clone_existing_web_process(app, revision)
health_check_type: web_process.health_check_type,
health_check_http_endpoint: web_process.health_check_http_endpoint,
health_check_invocation_timeout: web_process.health_check_invocation_timeout,
health_check_interval: web_process.health_check_interval,
readiness_health_check_type: web_process.readiness_health_check_type,
readiness_health_check_http_endpoint: web_process.readiness_health_check_http_endpoint,
readiness_health_check_invocation_timeout: web_process.readiness_health_check_invocation_timeout,
readiness_health_check_interval: web_process.readiness_health_check_interval,
enable_ssh: web_process.enable_ssh,
ports: web_process.ports,
revision: revision,
Expand Down
19 changes: 19 additions & 0 deletions app/actions/process_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def initialize(user_audit_info, manifest_triggered: false)
@manifest_triggered = manifest_triggered
end

# rubocop:disable Metrics/CyclomaticComplexity
def update(process, message, strategy_class)
if process.web? && process.app.deploying?
raise InvalidProcess.new('Cannot update this process while a deployment is in flight.')
Expand All @@ -26,9 +27,16 @@ def update(process, message, strategy_class)
process.skip_process_version_update = true
end

if message.requested?(:readiness_health_check_type)
process.readiness_health_check_type = message.readiness_health_check_type
process.skip_process_version_update = true
end

process.command = strategy.updated_command if message.requested?(:command)
process.health_check_timeout = message.health_check_timeout if message.requested?(:health_check_timeout)

process.health_check_invocation_timeout = message.health_check_invocation_timeout if message.requested?(:health_check_invocation_timeout)
process.health_check_interval = message.health_check_interval if message.requested?(:health_check_interval)
if message.requested?(:health_check_type) && message.health_check_type != HealthCheckTypes::HTTP
process.health_check_http_endpoint = nil
process.skip_process_version_update = true
Expand All @@ -37,12 +45,23 @@ def update(process, message, strategy_class)
process.skip_process_version_update = true
end

process.readiness_health_check_invocation_timeout = message.readiness_health_check_invocation_timeout if message.requested?(:readiness_health_check_invocation_timeout)
process.readiness_health_check_interval = message.readiness_health_check_interval if message.requested?(:readiness_health_check_interval)
if message.requested?(:readiness_health_check_type) && message.readiness_health_check_type != HealthCheckTypes::HTTP
process.readiness_health_check_http_endpoint = nil
process.skip_process_version_update = true
elsif message.requested?(:readiness_health_check_endpoint)
process.readiness_health_check_http_endpoint = message.readiness_health_check_endpoint
process.skip_process_version_update = true
end

process.save

Repositories::ProcessEventRepository.record_update(process, @user_audit_info, message.audit_hash, manifest_triggered: @manifest_triggered)
end
rescue Sequel::ValidationFailed => e
raise InvalidProcess.new(e.message)
end
# rubocop:enable Metrics/CyclomaticComplexity
end
end
5 changes: 5 additions & 0 deletions app/actions/space_diff_manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ def filter_manifest_app_hash(manifest_app_hash)
'log-rate-limit-per-second',
'health-check-http-endpoint',
'health-check-invocation-timeout',
'health-check-interval',
'health-check-type',
'readiness-health-check-http-endpoint',
'readiness-health-check-invocation-timeout',
'readiness-health-check-interval',
'readiness-health-check-type',
'instances',
'memory',
'timeout'
Expand Down
23 changes: 23 additions & 0 deletions app/messages/app_manifest_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@ class AppManifestMessage < BaseMessage
:env,
:health_check_http_endpoint,
:health_check_invocation_timeout,
:health_check_interval,
:health_check_timeout,
:health_check_type,
:readiness_health_check_http_endpoint,
:readiness_health_check_type,
:readiness_health_check_invocation_timeout,
:readiness_health_check_interval,
:instances,
:metadata,
:memory,
Expand Down Expand Up @@ -208,28 +213,46 @@ def process_update_attributes_from_app_level
mapping[:health_check_http_endpoint] = health_check_http_endpoint if requested?(:health_check_http_endpoint)
mapping[:timeout] = timeout if requested?(:timeout)
mapping[:health_check_invocation_timeout] = health_check_invocation_timeout if requested?(:health_check_invocation_timeout)
mapping[:health_check_interval] = health_check_interval if requested?(:health_check_interval)
mapping[:readiness_health_check_invocation_timeout] = readiness_health_check_invocation_timeout if requested?(:readiness_health_check_invocation_timeout)
mapping[:readiness_health_check_interval] = readiness_health_check_interval if requested?(:readiness_health_check_interval)
mapping[:readiness_health_check_http_endpoint] = readiness_health_check_http_endpoint if requested?(:readiness_health_check_http_endpoint)

if requested?(:health_check_type)
mapping[:health_check_type] = converted_health_check_type(health_check_type)
end

if requested?(:readiness_health_check_type)
mapping[:readiness_health_check_type] = converted_health_check_type(readiness_health_check_type)
end

mapping
end

# rubocop:disable Metrics/CyclomaticComplexity
def process_update_attributes_from_process(params)
mapping = {}
mapping[:command] = params[:command] || 'null' if params.key?(:command)
mapping[:health_check_http_endpoint] = params[:health_check_http_endpoint] if params.key?(:health_check_http_endpoint)
mapping[:health_check_timeout] = params[:health_check_timeout] if params.key?(:health_check_timeout)
mapping[:health_check_invocation_timeout] = params[:health_check_invocation_timeout] if params.key?(:health_check_invocation_timeout)
mapping[:health_check_interval] = params[:health_check_interval] if params.key?(:health_check_interval)
mapping[:readiness_health_check_invocation_timeout] = params[:readiness_health_check_invocation_timeout] if params.key?(:readiness_health_check_invocation_timeout)
mapping[:readiness_health_check_interval] = params[:readiness_health_check_interval] if params.key?(:readiness_health_check_interval)
mapping[:readiness_health_check_http_endpoint] = params[:readiness_health_check_http_endpoint] if params.key?(:readiness_health_check_http_endpoint)
mapping[:timeout] = params[:timeout] if params.key?(:timeout)
mapping[:type] = params[:type]

if params.key?(:health_check_type)
mapping[:health_check_type] = converted_health_check_type(params[:health_check_type])
mapping[:health_check_timeout] = params[:health_check_timeout] if params.key?(:health_check_timeout)
end
if params.key?(:readiness_health_check_type)
mapping[:readiness_health_check_type] = converted_health_check_type(params[:readiness_health_check_type])
end
mapping
end
# rubocop:enable Metrics/CyclomaticComplexity

def env_update_attribute_mapping
mapping = {}
Expand Down
53 changes: 52 additions & 1 deletion app/messages/manifest_process_update_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,31 @@

module VCAP::CloudController
class ManifestProcessUpdateMessage < BaseMessage
register_allowed_keys [:command, :health_check_type, :health_check_http_endpoint, :health_check_invocation_timeout, :timeout, :type]
register_allowed_keys [
:command,
:health_check_http_endpoint,
:health_check_invocation_timeout,
:health_check_type,
:health_check_interval,
:readiness_health_check_http_endpoint,
:readiness_health_check_invocation_timeout,
:readiness_health_check_type,
:readiness_health_check_interval,
:timeout,
:type
]

def self.health_check_endpoint_and_type_requested?
proc { |a| a.requested?(:health_check_type) && a.requested?(:health_check_http_endpoint) }
end

def self.readiness_health_check_endpoint_and_type_requested?
proc { |a| a.requested?(:readiness_health_check_type) && a.requested?(:readiness_health_check_http_endpoint) }
end

validates_with NoAdditionalKeysValidator
validates_with HealthCheckValidator, if: health_check_endpoint_and_type_requested?
validates_with ReadinessHealthCheckValidator, if: readiness_health_check_endpoint_and_type_requested?

validates :command,
string: true,
Expand All @@ -36,6 +53,35 @@ def self.health_check_endpoint_and_type_requested?
numericality: { only_integer: true, greater_than_or_equal_to: 1 },
if: proc { |a| a.requested?(:health_check_invocation_timeout) }

validates :health_check_interval,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 },
if: proc { |a| a.requested?(:health_check_interval) }

validates :readiness_health_check_type,
inclusion: {
in: [HealthCheckTypes::PORT, HealthCheckTypes::PROCESS, HealthCheckTypes::HTTP],
message: 'must be "port", "process", or "http"'
},
if: proc { |a| a.requested?(:readiness_health_check_type) }

validates :readiness_health_check_invocation_timeout,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 },
if: proc { |a| a.requested?(:readiness_health_check_invocation_timeout) }

# This code implicitly depends on class UriPathValidator
# See gem active_model/validations/validates.rb: validates(*attributes) for details
validates :readiness_health_check_http_endpoint,
allow_nil: true,
uri_path: true,
if: proc { |a| a.requested?(:readiness_health_check_http_endpoint) }

validates :readiness_health_check_interval,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 },
if: proc { |a| a.requested?(:readiness_health_check_interval) }

validates :timeout,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 },
Expand All @@ -45,6 +91,7 @@ def initialize(params={})
super(params)
@requested_keys << :health_check_timeout if requested? :timeout
@requested_keys << :health_check_endpoint if requested? :health_check_http_endpoint
@requested_keys << :readiness_health_check_endpoint if requested? :readiness_health_check_http_endpoint
end

def health_check_endpoint
Expand All @@ -54,5 +101,9 @@ def health_check_endpoint
def health_check_timeout
timeout
end

def readiness_health_check_endpoint
readiness_health_check_http_endpoint
end
end
end
65 changes: 63 additions & 2 deletions app/messages/process_update_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@

module VCAP::CloudController
class ProcessUpdateMessage < MetadataBaseMessage
register_allowed_keys [:command, :health_check]
register_allowed_keys [:command, :health_check, :readiness_health_check]

# rubocop:disable Metrics/CyclomaticComplexity
def initialize(params={})
super(params)
params = params.deep_symbolize_keys
@requested_keys << :health_check_type if HashUtils.dig(params, :health_check)&.key?(:type)
@requested_keys << :health_check_timeout if HashUtils.dig(params, :health_check, :data)&.key?(:timeout)
@requested_keys << :health_check_invocation_timeout if HashUtils.dig(params, :health_check, :data)&.key?(:invocation_timeout)
@requested_keys << :health_check_interval if HashUtils.dig(params, :health_check, :data)&.key?(:interval)
@requested_keys << :health_check_endpoint if HashUtils.dig(params, :health_check, :data)&.key?(:endpoint)
@requested_keys << :readiness_health_check_type if HashUtils.dig(params, :readiness_health_check)&.key?(:type)
@requested_keys << :readiness_health_check_invocation_timeout if HashUtils.dig(params, :readiness_health_check, :data)&.key?(:invocation_timeout)
@requested_keys << :readiness_health_check_interval if HashUtils.dig(params, :readiness_health_check, :data)&.key?(:interval)
@requested_keys << :readiness_health_check_endpoint if HashUtils.dig(params, :readiness_health_check, :data)&.key?(:endpoint)
end
# rubocop:enable Metrics/CyclomaticComplexity

def self.command_requested?
@command_requested ||= proc { |a| a.requested?(:command) }
Expand Down Expand Up @@ -41,11 +48,35 @@ def self.command_requested?
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DB_INT }

validates :health_check_interval,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DB_INT }

validates :health_check_endpoint,
length: { maximum: 255 },
allow_nil: true,
uri_path: true

validates :readiness_health_check_type,
inclusion: {
in: [HealthCheckTypes::PORT, HealthCheckTypes::PROCESS, HealthCheckTypes::HTTP],
message: 'must be "port", "process", or "http"'
},
if: -> { readiness_health_check && readiness_health_check.key?(:type) }

validates :readiness_health_check_invocation_timeout,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DB_INT }

validates :readiness_health_check_interval,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DB_INT }

validates :readiness_health_check_endpoint,
length: { maximum: 255 },
allow_nil: true,
uri_path: true

def health_check_type
HashUtils.dig(health_check, :type)
end
Expand All @@ -58,12 +89,42 @@ def health_check_invocation_timeout
HashUtils.dig(health_check, :data, :invocation_timeout)
end

def health_check_interval
HashUtils.dig(health_check, :data, :interval)
end

def health_check_endpoint
HashUtils.dig(health_check, :data, :endpoint)
end

def readiness_health_check_type
HashUtils.dig(readiness_health_check, :type)
end

def readiness_health_check_invocation_timeout
HashUtils.dig(readiness_health_check, :data, :invocation_timeout)
end

def readiness_health_check_interval
HashUtils.dig(readiness_health_check, :data, :interval)
end

def readiness_health_check_endpoint
HashUtils.dig(readiness_health_check, :data, :endpoint)
end

def audit_hash
super(exclude: [:health_check_type, :health_check_timeout, :health_check_invocation_timeout, :health_check_endpoint])
super(exclude: [
:health_check_type,
:health_check_timeout,
:health_check_invocation_timeout,
:health_check_interval,
:health_check_endpoint,
:readiness_health_check_type,
:readiness_health_check_invocation_timeout,
:readiness_health_check_interval,
:readiness_health_check_endpoint
])
end
end
end
8 changes: 8 additions & 0 deletions app/messages/validators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ def validate(record)
end
end

class ReadinessHealthCheckValidator < ActiveModel::Validator
def validate(record)
if record.readiness_health_check_type != VCAP::CloudController::HealthCheckTypes::HTTP
record.errors.add(:readiness_health_check_type, message: 'must be "http" to set a health check HTTP endpoint')
end
end
end

class OrgVisibilityValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.nil?
Expand Down
2 changes: 2 additions & 0 deletions app/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
require 'models/runtime/constraints/instances_policy'
require 'models/runtime/constraints/max_app_instances_policy'
require 'models/runtime/constraints/max_app_tasks_policy'
require 'models/runtime/constraints/base_health_check_policy'
require 'models/runtime/constraints/health_check_policy'
require 'models/runtime/constraints/readiness_health_check_policy'
require 'models/runtime/constraints/docker_policy'
require 'models/runtime/constraints/sidecar_memory_less_than_process_memory_policy'
require 'models/runtime/revision_model'
Expand Down
13 changes: 13 additions & 0 deletions app/models/helpers/health_check_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,18 @@ class HealthCheckTypes
PROCESS = 'process'.freeze
HTTP = 'http'.freeze
NONE = 'none'.freeze

def self.all_types
[
HTTP,
NONE,
PORT,
PROCESS,
]
end

def self.readiness_types
self.all_types - [NONE]
end
end
end
Loading