diff --git a/app/actions/build_create.rb b/app/actions/build_create.rb
index 379f4643ca9..8bce29f0571 100644
--- a/app/actions/build_create.rb
+++ b/app/actions/build_create.rb
@@ -1,3 +1,4 @@
+require 'cloud_controller/backends/quota_validating_staging_log_rate_limit_calculator'
require 'cloud_controller/backends/quota_validating_staging_memory_calculator'
require 'cloud_controller/backends/staging_disk_calculator'
require 'cloud_controller/backends/staging_environment_builder'
@@ -11,12 +12,16 @@ class BuildError < StandardError
end
class InvalidPackage < BuildError
end
- class SpaceQuotaExceeded < BuildError
+ class MemorySpaceQuotaExceeded < BuildError
end
- class OrgQuotaExceeded < BuildError
+ class MemoryOrgQuotaExceeded < BuildError
end
class DiskLimitExceeded < BuildError
end
+ class LogRateLimitSpaceQuotaExceeded < BuildError
+ end
+ class LogRateLimitOrgQuotaExceeded < BuildError
+ end
class StagingInProgress < BuildError
end
@@ -25,12 +30,14 @@ class StagingInProgress < BuildError
def initialize(user_audit_info: UserAuditInfo.from_context(SecurityContext),
memory_limit_calculator: QuotaValidatingStagingMemoryCalculator.new,
disk_limit_calculator: StagingDiskCalculator.new,
+ log_rate_limit_calculator: QuotaValidatingStagingLogRateLimitCalculator.new,
environment_presenter: StagingEnvironmentBuilder.new)
@user_audit_info = user_audit_info
@memory_limit_calculator = memory_limit_calculator
@disk_limit_calculator = disk_limit_calculator
- @environment_builder = environment_presenter
+ @log_rate_limit_calculator = log_rate_limit_calculator
+ @environment_builder = environment_presenter
end
def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: false)
@@ -49,6 +56,7 @@ def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: f
app: package.app,
staging_memory_in_mb: staging_details.staging_memory_in_mb,
staging_disk_in_mb: staging_details.staging_disk_in_mb,
+ staging_log_rate_limit: staging_details.staging_log_rate_limit_bytes_per_second,
created_by_user_guid: @user_audit_info.user_guid,
created_by_user_name: @user_audit_info.user_name,
created_by_user_email: @user_audit_info.user_email
@@ -117,6 +125,7 @@ def get_staging_details(package, lifecycle)
memory_limit = get_memory_limit(lifecycle.staging_message.staging_memory_in_mb, app, space, org)
disk_limit = get_disk_limit(lifecycle.staging_message.staging_disk_in_mb, app)
+ log_rate_limit = get_log_rate_limit(lifecycle.staging_message.staging_log_rate_limit_bytes_per_second, app, space, org)
environment_variables = @environment_builder.build(app,
space,
lifecycle,
@@ -128,6 +137,7 @@ def get_staging_details(package, lifecycle)
staging_details.package = package
staging_details.staging_memory_in_mb = memory_limit
staging_details.staging_disk_in_mb = disk_limit
+ staging_details.staging_log_rate_limit_bytes_per_second = log_rate_limit
staging_details.environment_variables = environment_variables
staging_details.lifecycle = lifecycle
staging_details.isolation_segment = IsolationSegmentSelector.for_space(space)
@@ -146,9 +156,18 @@ def get_memory_limit(requested_limit, app, space, org)
limit = requested_limit || app.newest_web_process&.memory
@memory_limit_calculator.get_limit(limit, space, org)
rescue QuotaValidatingStagingMemoryCalculator::SpaceQuotaExceeded => e
- raise SpaceQuotaExceeded.new e.message
+ raise MemorySpaceQuotaExceeded.new e.message
rescue QuotaValidatingStagingMemoryCalculator::OrgQuotaExceeded => e
- raise OrgQuotaExceeded.new e.message
+ raise MemoryOrgQuotaExceeded.new e.message
+ end
+
+ def get_log_rate_limit(requested_limit, app, space, org)
+ limit = requested_limit || app.newest_web_process&.log_rate_limit
+ @log_rate_limit_calculator.get_limit(limit, space, org)
+ rescue QuotaValidatingStagingLogRateLimitCalculator::SpaceQuotaExceeded => e
+ raise LogRateLimitSpaceQuotaExceeded.new e.message
+ rescue QuotaValidatingStagingLogRateLimitCalculator::OrgQuotaExceeded => e
+ raise LogRateLimitOrgQuotaExceeded.new e.message
end
def logger
diff --git a/app/actions/deployment_create.rb b/app/actions/deployment_create.rb
index 9f69f9be8d7..af04731279d 100644
--- a/app/actions/deployment_create.rb
+++ b/app/actions/deployment_create.rb
@@ -109,6 +109,7 @@ def clone_existing_web_process(app, revision)
memory: web_process.memory,
file_descriptors: web_process.file_descriptors,
disk_quota: web_process.disk_quota,
+ log_rate_limit: web_process.log_rate_limit,
metadata: web_process.metadata, # execution_metadata, not labels & annotations metadata!
detected_buildpack: web_process.detected_buildpack,
health_check_timeout: web_process.health_check_timeout,
diff --git a/app/actions/organization_quotas_create.rb b/app/actions/organization_quotas_create.rb
index 07e0096dc04..cf3f7e85815 100644
--- a/app/actions/organization_quotas_create.rb
+++ b/app/actions/organization_quotas_create.rb
@@ -16,6 +16,7 @@ def create(message)
instance_memory_limit: message.per_process_memory_in_mb || QuotaDefinition::UNLIMITED,
app_instance_limit: message.total_instances || QuotaDefinition::UNLIMITED,
app_task_limit: message.per_app_tasks || QuotaDefinition::UNLIMITED,
+ log_rate_limit: message.log_rate_limit_in_bytes_per_second || QuotaDefinition::UNLIMITED,
# Services
total_services: message.total_service_instances || QuotaDefinition::DEFAULT_TOTAL_SERVICES,
diff --git a/app/actions/organization_quotas_update.rb b/app/actions/organization_quotas_update.rb
index 0cf74c7d98f..12f7cfcd35c 100644
--- a/app/actions/organization_quotas_update.rb
+++ b/app/actions/organization_quotas_update.rb
@@ -13,6 +13,7 @@ def self.update(quota, message)
quota.instance_memory_limit = instance_memory_limit(message) if message.apps_limits_message.requested? :per_process_memory_in_mb
quota.app_instance_limit = app_instance_limit(message) if message.apps_limits_message.requested? :total_instances
quota.app_task_limit = app_task_limit(message) if message.apps_limits_message.requested? :per_app_tasks
+ quota.log_rate_limit = log_rate_limit(message) if message.apps_limits_message.requested? :log_rate_limit_in_bytes_per_second
quota.total_services = total_services(message) if message.services_limits_message.requested? :total_service_instances
quota.total_service_keys = total_service_keys(message) if message.services_limits_message.requested? :total_service_keys
@@ -56,6 +57,10 @@ def self.app_task_limit(message)
default_if_nil(message.per_app_tasks, QuotaDefinition::UNLIMITED)
end
+ def self.log_rate_limit(message)
+ default_if_nil(message.log_rate_limit_in_bytes_per_second, QuotaDefinition::UNLIMITED)
+ end
+
def self.total_services(message)
default_if_nil(message.total_service_instances, QuotaDefinition::UNLIMITED)
end
diff --git a/app/actions/process_scale.rb b/app/actions/process_scale.rb
index d13362fb9d9..42d349b0746 100644
--- a/app/actions/process_scale.rb
+++ b/app/actions/process_scale.rb
@@ -22,7 +22,9 @@ def scale
@process.instances = @message.instances if @message.requested?(:instances)
@process.memory = @message.memory_in_mb if @message.requested?(:memory_in_mb)
@process.disk_quota = @message.disk_in_mb if @message.requested?(:disk_in_mb)
-
+ if @message.requested?(:log_rate_limit_in_bytes_per_second) && !@message.log_rate_limit_in_bytes_per_second.nil?
+ @process.log_rate_limit = @message.log_rate_limit_in_bytes_per_second
+ end
@process.save
record_audit_event
diff --git a/app/actions/space_diff_manifest.rb b/app/actions/space_diff_manifest.rb
index 5a860535b46..ec791f32711 100644
--- a/app/actions/space_diff_manifest.rb
+++ b/app/actions/space_diff_manifest.rb
@@ -14,8 +14,10 @@ class << self
# rubocop:todo Metrics/CyclomaticComplexity
def generate_diff(app_manifests, space)
json_diff = []
- recognized_top_level_keys = AppManifestMessage.allowed_keys.map(&:to_s)
- app_manifests = convert_byte_measurements_to_mb(app_manifests)
+
+ recognized_top_level_keys = AppManifestMessage.allowed_keys.map(&:to_s).map(&:dasherize)
+
+ app_manifests = normalize_units(app_manifests)
app_manifests.each_with_index do |manifest_app_hash, index|
manifest_app_hash = filter_manifest_app_hash(manifest_app_hash)
existing_app = space.app_models.find { |app| app.name == manifest_app_hash['name'] }
@@ -90,7 +92,7 @@ def filter_manifest_app_hash(manifest_app_hash)
'memory'
)
end
- manifest_app_hash['sidecars'] = convert_byte_measurements_to_mb(manifest_app_hash['sidecars'])
+ manifest_app_hash['sidecars'] = normalize_units(manifest_app_hash['sidecars'])
manifest_app_hash = manifest_app_hash.except('sidecars') if manifest_app_hash['sidecars'] == [{}]
end
if manifest_app_hash.key? 'processes'
@@ -99,6 +101,7 @@ def filter_manifest_app_hash(manifest_app_hash)
'type',
'command',
'disk_quota',
+ 'log-rate-limit-per-second',
'health-check-http-endpoint',
'health-check-invocation-timeout',
'health-check-type',
@@ -107,7 +110,7 @@ def filter_manifest_app_hash(manifest_app_hash)
'timeout'
)
end
- manifest_app_hash['processes'] = convert_byte_measurements_to_mb(manifest_app_hash['processes'])
+ manifest_app_hash['processes'] = normalize_units(manifest_app_hash['processes'])
manifest_app_hash = manifest_app_hash.except('processes') if manifest_app_hash['processes'] == [{}]
end
@@ -151,7 +154,7 @@ def create_similarity
end
end
- def convert_byte_measurements_to_mb(manifest_app_hash)
+ def normalize_units(manifest_app_hash)
byte_measurement_key_words = ['memory', 'disk-quota', 'disk_quota']
manifest_app_hash.each_with_index do |process_hash, index|
byte_measurement_key_words.each do |key|
@@ -159,6 +162,14 @@ def convert_byte_measurements_to_mb(manifest_app_hash)
manifest_app_hash[index][key] = convert_to_mb(value, key) unless value.nil?
end
end
+
+ byte_measurement_key_words = ['log-rate-limit-per-second']
+ manifest_app_hash.each_with_index do |process_hash, index|
+ byte_measurement_key_words.each do |key|
+ value = process_hash[key]
+ manifest_app_hash[index][key] = normalize_unit(value, key) unless value.nil?
+ end
+ end
manifest_app_hash
end
@@ -170,6 +181,18 @@ def convert_to_mb(human_readable_byte_value, attribute_name)
"#{attribute_name} is not a number"
end
+ def normalize_unit(non_normalized_value, attribute_name)
+ if %w(-1 0).include?(non_normalized_value.to_s)
+ non_normalized_value.to_s
+ else
+ byte_converter.human_readable_byte_value(byte_converter.convert_to_b(non_normalized_value))
+ end
+ rescue ByteConverter::InvalidUnitsError
+ "#{attribute_name} must use a supported unit: B, K, KB, M, MB, G, GB, T, or TB"
+ rescue ByteConverter::NonNumericError
+ "#{attribute_name} is not a number"
+ end
+
def byte_converter
ByteConverter.new
end
diff --git a/app/actions/space_quota_update.rb b/app/actions/space_quota_update.rb
index d20aee42a21..99c58cbee99 100644
--- a/app/actions/space_quota_update.rb
+++ b/app/actions/space_quota_update.rb
@@ -14,6 +14,7 @@ def self.update(quota, message)
quota.instance_memory_limit = instance_memory_limit(message) if message.apps_limits_message.requested? :per_process_memory_in_mb
quota.app_instance_limit = app_instance_limit(message) if message.apps_limits_message.requested? :total_instances
quota.app_task_limit = app_task_limit(message) if message.apps_limits_message.requested? :per_app_tasks
+ quota.log_rate_limit = log_rate_limit(message) if message.apps_limits_message.requested? :log_rate_limit_in_bytes_per_second
quota.total_services = total_services(message) if message.services_limits_message.requested? :total_service_instances
quota.total_service_keys = total_service_keys(message) if message.services_limits_message.requested? :total_service_keys
@@ -55,6 +56,10 @@ def self.app_task_limit(message)
default_if_nil(message.per_app_tasks, SpaceQuotaDefinition::UNLIMITED)
end
+ def self.log_rate_limit(message)
+ default_if_nil(message.log_rate_limit_in_bytes_per_second, SpaceQuotaDefinition::UNLIMITED)
+ end
+
def self.total_services(message)
default_if_nil(message.total_service_instances, SpaceQuotaDefinition::UNLIMITED)
end
diff --git a/app/actions/space_quotas_create.rb b/app/actions/space_quotas_create.rb
index 5b10fd617fd..fe33cfd1a4b 100644
--- a/app/actions/space_quotas_create.rb
+++ b/app/actions/space_quotas_create.rb
@@ -3,6 +3,7 @@ class SpaceQuotasCreate
class Error < ::StandardError
end
+ # rubocop:todo Metrics/CyclomaticComplexity
def create(message, organization:)
space_quota = nil
@@ -16,6 +17,7 @@ def create(message, organization:)
instance_memory_limit: message.per_process_memory_in_mb || SpaceQuotaDefinition::UNLIMITED,
app_instance_limit: message.total_instances || SpaceQuotaDefinition::UNLIMITED,
app_task_limit: message.per_app_tasks || SpaceQuotaDefinition::UNLIMITED,
+ log_rate_limit: message.log_rate_limit_in_bytes_per_second || QuotaDefinition::UNLIMITED,
# Services
total_services: message.total_service_instances || SpaceQuotaDefinition::DEFAULT_TOTAL_SERVICES,
@@ -35,6 +37,7 @@ def create(message, organization:)
rescue Sequel::ValidationFailed => e
validation_error!(e, message)
end
+ # rubocop:enable Metrics/CyclomaticComplexity
private
diff --git a/app/actions/task_create.rb b/app/actions/task_create.rb
index 39301b6711b..dae42de0508 100644
--- a/app/actions/task_create.rb
+++ b/app/actions/task_create.rb
@@ -29,6 +29,7 @@ def create(app, message, user_audit_info, droplet: nil)
command: command(message, template_process),
disk_in_mb: disk_in_mb(message, template_process),
memory_in_mb: memory_in_mb(message, template_process),
+ log_rate_limit: log_rate_limit(message, template_process),
sequence_id: app.max_task_sequence_id
)
@@ -70,15 +71,15 @@ def command(message, template_process)
end
def memory_in_mb(message, template_process)
- return message.memory_in_mb if message.memory_in_mb
+ message.memory_in_mb || template_process.try(:memory) || config.get(:default_app_memory)
+ end
- template_process.present? ? template_process.memory : config.get(:default_app_memory)
+ def log_rate_limit(message, template_process)
+ message.log_rate_limit_in_bytes_per_second || template_process.try(:log_rate_limit) || config.get(:default_app_log_rate_limit_in_bytes_per_second)
end
def disk_in_mb(message, template_process)
- return message.disk_in_mb if message.disk_in_mb
-
- template_process.present? ? template_process.disk_quota : config.get(:default_app_disk_in_mb)
+ message.disk_in_mb || template_process.try(:disk_quota) || config.get(:default_app_disk_in_mb)
end
def submit_task(task)
diff --git a/app/actions/v2/app_stage.rb b/app/actions/v2/app_stage.rb
index b46e220ddff..c9f2426cfcb 100644
--- a/app/actions/v2/app_stage.rb
+++ b/app/actions/v2/app_stage.rb
@@ -40,12 +40,16 @@ def stage(process)
process.last_stager_response = build_creator.staging_response
rescue Diego::Runner::CannotCommunicateWithDiegoError => e
logger.error("failed communicating with diego backend: #{e.message}")
- rescue BuildCreate::SpaceQuotaExceeded => e
+ rescue BuildCreate::MemorySpaceQuotaExceeded => e
raise CloudController::Errors::ApiError.new_from_details('SpaceQuotaMemoryLimitExceeded', e.message)
- rescue BuildCreate::OrgQuotaExceeded => e
+ rescue BuildCreate::MemoryOrgQuotaExceeded => e
raise CloudController::Errors::ApiError.new_from_details('AppMemoryQuotaExceeded', e.message)
rescue BuildCreate::DiskLimitExceeded
raise CloudController::Errors::ApiError.new_from_details('AppInvalid', 'too much disk requested')
+ rescue BuildCreate::LogRateLimitSpaceQuotaExceeded => e
+ raise CloudController::Errors::ApiError.new_from_details('SpaceQuotaLogRateLimitExceeded', e.message)
+ rescue BuildCreate::LogRateLimitOrgQuotaExceeded => e
+ raise CloudController::Errors::ApiError.new_from_details('OrgQuotaLogRateLimitExceeded', e.message)
rescue BuildCreate::BuildError => e
raise CloudController::Errors::ApiError.new_from_details('AppInvalid', e.message)
end
diff --git a/app/controllers/runtime/apps_controller.rb b/app/controllers/runtime/apps_controller.rb
index d8d2d046e36..9e475490ad7 100644
--- a/app/controllers/runtime/apps_controller.rb
+++ b/app/controllers/runtime/apps_controller.rb
@@ -33,6 +33,7 @@ def self.dependencies
attribute :docker_credentials, Hash, default: {}
attribute :debug, String, default: nil
attribute :disk_quota, Integer, default: nil
+ attribute :log_rate_limit, Integer, default: nil
attribute :environment_json, Hash, default: {}, redact_in: [:create, :update]
attribute :health_check_http_endpoint, String, default: nil
attribute :health_check_type, String, default: 'port'
diff --git a/app/controllers/v3/builds_controller.rb b/app/controllers/v3/builds_controller.rb
index 3195104dd69..74716b9e399 100644
--- a/app/controllers/v3/builds_controller.rb
+++ b/app/controllers/v3/builds_controller.rb
@@ -60,12 +60,16 @@ def create
render status: :created, json: Presenters::V3::BuildPresenter.new(build)
rescue BuildCreate::InvalidPackage => e
bad_request!(e.message)
- rescue BuildCreate::SpaceQuotaExceeded => e
+ rescue BuildCreate::MemorySpaceQuotaExceeded => e
unprocessable!("space's memory limit exceeded: #{e.message}")
- rescue BuildCreate::OrgQuotaExceeded => e
+ rescue BuildCreate::MemoryOrgQuotaExceeded => e
unprocessable!("organization's memory limit exceeded: #{e.message}")
rescue BuildCreate::DiskLimitExceeded
unprocessable!('disk limit exceeded')
+ rescue BuildCreate::LogRateLimitSpaceQuotaExceeded => e
+ unprocessable!("space's log rate limit exceeded: #{e.message}")
+ rescue BuildCreate::LogRateLimitOrgQuotaExceeded => e
+ unprocessable!("organization's log rate limit exceeded: #{e.message}")
rescue BuildCreate::StagingInProgress
raise CloudController::Errors::ApiError.new_from_details('StagingInProgress')
rescue BuildCreate::BuildError => e
diff --git a/app/controllers/v3/processes_controller.rb b/app/controllers/v3/processes_controller.rb
index f02977d7eac..59f627d8f31 100644
--- a/app/controllers/v3/processes_controller.rb
+++ b/app/controllers/v3/processes_controller.rb
@@ -89,6 +89,7 @@ def scale
'instance-count' => message.instances,
'memory-in-mb' => message.memory_in_mb,
'disk-in-mb' => message.disk_in_mb,
+ 'log-rate-in-bytes-per-second' => message.log_rate_limit_in_bytes_per_second,
'process-type' => @process.type
}
)
diff --git a/app/messages/app_manifest_message.rb b/app/messages/app_manifest_message.rb
index d4b02c257c2..9fce84c6971 100644
--- a/app/messages/app_manifest_message.rb
+++ b/app/messages/app_manifest_message.rb
@@ -16,6 +16,7 @@ class AppManifestMessage < BaseMessage
:buildpacks,
:command,
:disk_quota,
+ :log_rate_limit_per_second,
:docker,
:env,
:health_check_http_endpoint,
@@ -130,12 +131,16 @@ def manifest_buildpack_message
end
def process_scale_attribute_mappings
- process_scale_attributes_from_app_level = process_scale_attributes(memory: memory, disk_quota: disk_quota, instances: instances)
+ process_scale_attributes_from_app_level = process_scale_attributes(memory: memory,
+ disk_quota: disk_quota,
+ log_rate_limit_per_second: log_rate_limit_per_second,
+ instances: instances)
process_attributes(process_scale_attributes_from_app_level) do |process|
process_scale_attributes(
memory: process[:memory],
disk_quota: process[:disk_quota],
+ log_rate_limit_per_second: process[:log_rate_limit_per_second],
instances: process[:instances],
type: process[:type]
)
@@ -163,13 +168,15 @@ def process_attributes(app_attributes)
process_attributes
end
- def process_scale_attributes(memory: nil, disk_quota: nil, instances:, type: nil)
+ def process_scale_attributes(memory: nil, disk_quota: nil, log_rate_limit_per_second: nil, instances:, type: nil)
memory_in_mb = convert_to_mb(memory)
disk_in_mb = convert_to_mb(disk_quota)
+ log_rate_limit_in_bytes_per_second = convert_to_bytes_per_second(log_rate_limit_per_second)
{
instances: instances,
memory: memory_in_mb,
disk_quota: disk_in_mb,
+ log_rate_limit: log_rate_limit_in_bytes_per_second,
type: type
}.compact
end
@@ -283,8 +290,20 @@ def convert_to_mb(human_readable_byte_value)
rescue ByteConverter::InvalidUnitsError, ByteConverter::NonNumericError
end
- def validate_byte_format(human_readable_byte_value, attribute_name)
- byte_converter.convert_to_mb(human_readable_byte_value)
+ def convert_to_bytes_per_second(human_readable_byte_value)
+ human_readable_byte_value = human_readable_byte_value.to_s
+ return nil unless human_readable_byte_value.present?
+ return -1 if human_readable_byte_value.to_s == '-1'
+ return 0 if human_readable_byte_value.to_s == '0'
+
+ byte_converter.convert_to_b(human_readable_byte_value.strip)
+ rescue ByteConverter::InvalidUnitsError, ByteConverter::NonNumericError
+ end
+
+ def validate_byte_format(human_readable_byte_value, attribute_name, allow_unlimited: false)
+ unless allow_unlimited && (human_readable_byte_value.to_s == '-1' || human_readable_byte_value.to_s == '0')
+ byte_converter.convert_to_mb(human_readable_byte_value)
+ end
nil
rescue ByteConverter::InvalidUnitsError
@@ -378,8 +397,10 @@ def validate_processes!
type = process[:type]
memory_error = validate_byte_format(process[:memory], 'Memory')
disk_error = validate_byte_format(process[:disk_quota], 'Disk quota')
+ log_rate_limit_error = validate_byte_format(process[:log_rate_limit_per_second], 'Log rate limit per second', allow_unlimited: true)
add_process_error!(memory_error, type) if memory_error
add_process_error!(disk_error, type) if disk_error
+ add_process_error!(log_rate_limit_error, type) if log_rate_limit_error
end
end
@@ -400,8 +421,10 @@ def validate_sidecars!
def validate_top_level_web_process!
memory_error = validate_byte_format(memory, 'Memory')
disk_error = validate_byte_format(disk_quota, 'Disk quota')
+ log_rate_limit_error = validate_byte_format(log_rate_limit_per_second, 'Log rate limit per second', allow_unlimited: true)
add_process_error!(memory_error, ProcessTypes::WEB) if memory_error
add_process_error!(disk_error, ProcessTypes::WEB) if disk_error
+ add_process_error!(log_rate_limit_error, ProcessTypes::WEB) if log_rate_limit_error
end
def validate_buildpack_and_buildpacks_combination!
diff --git a/app/messages/base_message.rb b/app/messages/base_message.rb
index e798f14dce2..17922378621 100644
--- a/app/messages/base_message.rb
+++ b/app/messages/base_message.rb
@@ -8,6 +8,7 @@ class BaseMessage
include Validators
MAX_DB_INT = 2**31 - 1
+ MAX_DB_BIGINT = 2**63 - 1
attr_accessor :requested_keys, :extra_keys
diff --git a/app/messages/build_create_message.rb b/app/messages/build_create_message.rb
index cad48bc7d58..cd51643edc7 100644
--- a/app/messages/build_create_message.rb
+++ b/app/messages/build_create_message.rb
@@ -4,7 +4,7 @@
module VCAP::CloudController
class BuildCreateMessage < MetadataBaseMessage
- register_allowed_keys [:staging_memory_in_mb, :staging_disk_in_mb, :environment_variables, :lifecycle, :package]
+ register_allowed_keys [:staging_memory_in_mb, :staging_disk_in_mb, :staging_log_rate_limit_bytes_per_second, :environment_variables, :lifecycle, :package]
def self.lifecycle_requested?
@lifecycle_requested ||= proc { |a| a.requested?(:lifecycle) }
@@ -12,6 +12,7 @@ def self.lifecycle_requested?
validates :staging_disk_in_mb, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
validates :staging_memory_in_mb, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
+ validates :staging_log_rate_limit_bytes_per_second, numericality: { only_integer: true, greater_than_or_equal_to: -1, less_than_or_equal_to: MAX_DB_BIGINT }, allow_nil: true
validates_with NoAdditionalKeysValidator
validates_with LifecycleValidator, if: lifecycle_requested?
diff --git a/app/messages/manifest_process_scale_message.rb b/app/messages/manifest_process_scale_message.rb
index 6c916da69f6..1905f089f57 100644
--- a/app/messages/manifest_process_scale_message.rb
+++ b/app/messages/manifest_process_scale_message.rb
@@ -2,20 +2,26 @@
module VCAP::CloudController
class ManifestProcessScaleMessage < BaseMessage
- register_allowed_keys [:instances, :memory, :disk_quota, :type]
+ register_allowed_keys [:instances, :memory, :disk_quota, :log_rate_limit, :type]
INVALID_MB_VALUE_ERROR = 'must be greater than 0MB'.freeze
+ # NOTE: -1 is valid for log_rate_limit representing unlimited
+ INVALID_QUOTA_VALUE_ERROR = 'must be an integer greater than or equal to -1'.freeze
validates_with NoAdditionalKeysValidator
validates :instances, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2000000 }, allow_nil: true
validates :memory, numericality: { only_integer: true, greater_than: 0, message: INVALID_MB_VALUE_ERROR }, allow_nil: true
validates :disk_quota, numericality: { only_integer: true, greater_than: 0, message: INVALID_MB_VALUE_ERROR }, allow_nil: true
+ validates :log_rate_limit,
+ numericality: { only_integer: true, greater_than_or_equal_to: -1, less_than_or_equal_to: MAX_DB_BIGINT, message: INVALID_QUOTA_VALUE_ERROR },
+ allow_nil: true
def to_process_scale_message
ProcessScaleMessage.new({
instances: instances,
memory_in_mb: memory,
disk_in_mb: disk_quota,
+ log_rate_limit_in_bytes_per_second: log_rate_limit,
}.compact)
end
end
diff --git a/app/messages/organization_quotas_update_message.rb b/app/messages/organization_quotas_update_message.rb
index b13c10ef29a..536a970266a 100644
--- a/app/messages/organization_quotas_update_message.rb
+++ b/app/messages/organization_quotas_update_message.rb
@@ -26,7 +26,7 @@ def self.key_requested?(key)
validate :domains_validator, if: key_requested?(:domains)
# Apps validations
- delegate :total_memory_in_mb, :per_process_memory_in_mb, :total_instances, :per_app_tasks, to: :apps_limits_message
+ delegate :total_memory_in_mb, :per_process_memory_in_mb, :total_instances, :per_app_tasks, :log_rate_limit_in_bytes_per_second, to: :apps_limits_message
def validates_hash(key, sym)
return true if key.is_a?(Hash)
diff --git a/app/messages/process_scale_message.rb b/app/messages/process_scale_message.rb
index d57ca116b93..e6cd9f80804 100644
--- a/app/messages/process_scale_message.rb
+++ b/app/messages/process_scale_message.rb
@@ -2,12 +2,13 @@
module VCAP::CloudController
class ProcessScaleMessage < BaseMessage
- register_allowed_keys [:instances, :memory_in_mb, :disk_in_mb]
+ register_allowed_keys [:instances, :memory_in_mb, :disk_in_mb, :log_rate_limit_in_bytes_per_second]
validates_with NoAdditionalKeysValidator
validates :instances, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: MAX_DB_INT }, allow_nil: true
validates :memory_in_mb, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: MAX_DB_INT }, allow_nil: true
validates :disk_in_mb, numericality: { only_integer: true, greater_than: 0, less_than_or_equal_to: MAX_DB_INT }, allow_nil: true
+ validates :log_rate_limit_in_bytes_per_second, numericality: { only_integer: true, greater_than_or_equal_to: -1, less_than_or_equal_to: MAX_DB_BIGINT }, allow_nil: true
end
end
diff --git a/app/messages/quotas_apps_message.rb b/app/messages/quotas_apps_message.rb
index fe488dc319c..e1029133dc8 100644
--- a/app/messages/quotas_apps_message.rb
+++ b/app/messages/quotas_apps_message.rb
@@ -3,7 +3,7 @@
module VCAP::CloudController
class QuotasAppsMessage < BaseMessage
- register_allowed_keys [:total_memory_in_mb, :per_process_memory_in_mb, :total_instances, :per_app_tasks]
+ register_allowed_keys [:total_memory_in_mb, :per_process_memory_in_mb, :total_instances, :per_app_tasks, :log_rate_limit_in_bytes_per_second]
validates_with NoAdditionalKeysValidator
@@ -22,5 +22,9 @@ class QuotasAppsMessage < BaseMessage
validates :per_app_tasks,
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: MAX_DB_INT },
allow_nil: true
+
+ validates :log_rate_limit_in_bytes_per_second,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: MAX_DB_BIGINT },
+ allow_nil: true
end
end
diff --git a/app/messages/space_quota_update_message.rb b/app/messages/space_quota_update_message.rb
index dbb6728488d..de1f3c51fd3 100644
--- a/app/messages/space_quota_update_message.rb
+++ b/app/messages/space_quota_update_message.rb
@@ -22,7 +22,7 @@ def self.key_requested?(key)
validate :services_validator, if: key_requested?(:services)
validate :routes_validator, if: key_requested?(:routes)
- delegate :total_memory_in_mb, :per_process_memory_in_mb, :total_instances, :per_app_tasks, to: :apps_limits_message
+ delegate :total_memory_in_mb, :per_process_memory_in_mb, :total_instances, :per_app_tasks, :log_rate_limit_in_bytes_per_second, to: :apps_limits_message
delegate :paid_services_allowed, :total_service_instances, :total_service_keys, to: :services_limits_message
delegate :total_routes, :total_reserved_ports, to: :routes_limits_message
diff --git a/app/messages/task_create_message.rb b/app/messages/task_create_message.rb
index 5f230151e5d..8392f2030d0 100644
--- a/app/messages/task_create_message.rb
+++ b/app/messages/task_create_message.rb
@@ -2,7 +2,7 @@
module VCAP::CloudController
class TaskCreateMessage < MetadataBaseMessage
- register_allowed_keys [:name, :command, :disk_in_mb, :memory_in_mb, :droplet_guid, :template]
+ register_allowed_keys [:name, :command, :disk_in_mb, :memory_in_mb, :log_rate_limit_in_bytes_per_second, :droplet_guid, :template]
validates_with NoAdditionalKeysValidator
@@ -12,6 +12,7 @@ def self.validate_template?
validates :disk_in_mb, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
validates :memory_in_mb, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
+ validates :log_rate_limit_in_bytes_per_second, numericality: { only_integer: true, greater_than: -2, less_than_or_equal_to: MAX_DB_BIGINT }, allow_nil: true
validates :droplet_guid, guid: true, allow_nil: true
validates :template_process_guid, guid: true, if: validate_template?
validate :has_command
diff --git a/app/models.rb b/app/models.rb
index 6c1fe448fb6..34672088446 100644
--- a/app/models.rb
+++ b/app/models.rb
@@ -47,6 +47,8 @@
require 'models/runtime/quota_constraints/max_service_keys_policy'
require 'models/runtime/constraints/max_disk_quota_policy'
require 'models/runtime/constraints/min_disk_quota_policy'
+require 'models/runtime/constraints/min_log_rate_limit_policy'
+require 'models/runtime/constraints/max_log_rate_limit_policy'
require 'models/runtime/constraints/max_memory_policy'
require 'models/runtime/constraints/max_instance_memory_policy'
require 'models/runtime/constraints/min_memory_policy'
diff --git a/app/models/runtime/constraints/max_app_instances_policy.rb b/app/models/runtime/constraints/max_app_instances_policy.rb
index 2a591662181..8ae62497b90 100644
--- a/app/models/runtime/constraints/max_app_instances_policy.rb
+++ b/app/models/runtime/constraints/max_app_instances_policy.rb
@@ -11,7 +11,7 @@ def initialize(process, space_or_org, quota_definition, error_name)
def validate
return unless @quota_definition
- return unless @process.scaling_operation?
+ return unless @process.started?
return if @quota_definition.app_instance_limit == -1 || @process.stopped?
other_apps = @space_or_org.processes.reject { |process| process.guid == @process.guid }
diff --git a/app/models/runtime/constraints/max_instance_memory_policy.rb b/app/models/runtime/constraints/max_instance_memory_policy.rb
index b5a8956e798..5ac48a24a2e 100644
--- a/app/models/runtime/constraints/max_instance_memory_policy.rb
+++ b/app/models/runtime/constraints/max_instance_memory_policy.rb
@@ -39,7 +39,7 @@ class AppMaxInstanceMemoryPolicy < BaseMaxInstanceMemoryPolicy
private
def additional_checks
- resource.scaling_operation?
+ resource.started?
end
end
diff --git a/app/models/runtime/constraints/max_log_rate_limit_policy.rb b/app/models/runtime/constraints/max_log_rate_limit_policy.rb
new file mode 100644
index 00000000000..a5a6ea14c0a
--- /dev/null
+++ b/app/models/runtime/constraints/max_log_rate_limit_policy.rb
@@ -0,0 +1,78 @@
+require 'cloud_controller/app_services/process_log_rate_limit_calculator'
+
+class BaseMaxLogRateLimitPolicy
+ def initialize(resource, policy_target, error_name)
+ @resource = resource
+ @policy_target = policy_target
+ @error_name = error_name
+ end
+
+ def validate
+ return unless policy_target
+ return unless additional_checks
+
+ if requested_log_rate_limit == VCAP::CloudController::QuotaDefinition::UNLIMITED &&
+ policy_target.log_rate_limit != VCAP::CloudController::QuotaDefinition::UNLIMITED
+
+ policy_target_type = if policy_target.respond_to?(:organization_guid)
+ 'space'
+ else
+ 'organization'
+ end
+
+ resource.errors.add(field, "cannot be unlimited in #{policy_target_type} '#{policy_target.name}'.")
+ end
+
+ unless policy_target.has_remaining_log_rate_limit(requested_log_rate_limit)
+ resource.errors.add(field, error_name)
+ end
+ end
+
+ private
+
+ attr_reader :resource, :policy_target, :error_name
+
+ def additional_checks
+ true
+ end
+
+ def requested_log_rate_limit
+ resource.public_send field
+ end
+
+ def field
+ :log_rate_limit
+ end
+end
+
+class AppMaxLogRateLimitPolicy < BaseMaxLogRateLimitPolicy
+ private
+
+ def additional_checks
+ resource.started? &&
+ (resource.column_changed?(:state) || resource.column_changed?(:instances))
+ end
+
+ def requested_log_rate_limit
+ calculator = VCAP::CloudController::ProcessLogRateLimitCalculator.new(resource)
+ calculator.additional_log_rate_limit_requested
+ end
+end
+
+class TaskMaxLogRateLimitPolicy < BaseMaxLogRateLimitPolicy
+ IGNORED_STATES = [
+ VCAP::CloudController::TaskModel::CANCELING_STATE,
+ VCAP::CloudController::TaskModel::SUCCEEDED_STATE,
+ VCAP::CloudController::TaskModel::FAILED_STATE,
+ ].freeze
+
+ private
+
+ def additional_checks
+ IGNORED_STATES.exclude?(resource.state)
+ end
+
+ def field
+ :log_rate_limit
+ end
+end
diff --git a/app/models/runtime/constraints/max_memory_policy.rb b/app/models/runtime/constraints/max_memory_policy.rb
index 6209953f4ee..00163e0350f 100644
--- a/app/models/runtime/constraints/max_memory_policy.rb
+++ b/app/models/runtime/constraints/max_memory_policy.rb
@@ -37,7 +37,7 @@ class AppMaxMemoryPolicy < BaseMaxMemoryPolicy
private
def additional_checks
- resource.scaling_operation?
+ resource.started?
end
def requested_memory
diff --git a/app/models/runtime/constraints/min_log_rate_limit_policy.rb b/app/models/runtime/constraints/min_log_rate_limit_policy.rb
new file mode 100644
index 00000000000..62c2dbfdb66
--- /dev/null
+++ b/app/models/runtime/constraints/min_log_rate_limit_policy.rb
@@ -0,0 +1,16 @@
+class MinLogRateLimitPolicy
+ ERROR_MSG = 'log_rate_limit must be greater than or equal to -1 (where -1 is unlimited)'.freeze
+
+ def initialize(process)
+ @process = process
+ @errors = process.errors
+ end
+
+ def validate
+ return unless @process.log_rate_limit
+
+ if @process.log_rate_limit < -1
+ @errors.add(:log_rate_limit, ERROR_MSG)
+ end
+ end
+end
diff --git a/app/models/runtime/organization.rb b/app/models/runtime/organization.rb
index 661f5382e56..755257323e4 100644
--- a/app/models/runtime/organization.rb
+++ b/app/models/runtime/organization.rb
@@ -230,13 +230,21 @@ def memory_used
end
def has_remaining_memory(mem)
- quota_definition.memory_limit == -1 || memory_remaining >= mem
+ quota_definition.memory_limit == QuotaDefinition::UNLIMITED || memory_remaining >= mem
+ end
+
+ def has_remaining_log_rate_limit(log_rate_limit_desired)
+ quota_definition.log_rate_limit == QuotaDefinition::UNLIMITED || log_rate_limit_remaining >= log_rate_limit_desired
end
def instance_memory_limit
quota_definition ? quota_definition.instance_memory_limit : QuotaDefinition::UNLIMITED
end
+ def log_rate_limit
+ quota_definition ? quota_definition.log_rate_limit : QuotaDefinition::UNLIMITED
+ end
+
def app_task_limit
quota_definition ? quota_definition.app_task_limit : QuotaDefinition::UNLIMITED
end
@@ -314,6 +322,10 @@ def memory_remaining
quota_definition.memory_limit - memory_used
end
+ def log_rate_limit_remaining
+ quota_definition.log_rate_limit - (started_app_log_rate_limit + running_task_log_rate_limit)
+ end
+
def running_task_memory
tasks_dataset.where(state: TaskModel::RUNNING_STATE).sum(:memory_in_mb) || 0
end
@@ -322,6 +334,14 @@ def started_app_memory
processes_dataset.where(state: ProcessModel::STARTED).sum(Sequel.*(:memory, :instances)) || 0
end
+ def running_task_log_rate_limit
+ tasks_dataset.where(state: TaskModel::RUNNING_STATE).sum(:log_rate_limit) || 0
+ end
+
+ def started_app_log_rate_limit
+ processes_dataset.where(state: ProcessModel::STARTED).sum(Sequel.*(:log_rate_limit, :instances)) || 0
+ end
+
def running_and_pending_tasks_count
tasks_dataset.where(state: [TaskModel::PENDING_STATE, TaskModel::RUNNING_STATE]).count
end
diff --git a/app/models/runtime/process_model.rb b/app/models/runtime/process_model.rb
index f454a0ed73e..097e346a25e 100644
--- a/app/models/runtime/process_model.rb
+++ b/app/models/runtime/process_model.rb
@@ -27,6 +27,7 @@ def after_initialize
self.memory ||= Config.config.get(:default_app_memory)
self.disk_quota ||= Config.config.get(:default_app_disk_in_mb)
self.file_descriptors ||= Config.config.get(:instance_file_descriptor_limit)
+ self.log_rate_limit ||= Config.config.get(:default_app_log_rate_limit_in_bytes_per_second)
self.metadata ||= {}
end
@@ -144,17 +145,19 @@ def non_docker_type
add_association_dependencies annotations: :destroy
export_attributes :name, :production, :space_guid, :stack_guid, :buildpack,
- :detected_buildpack, :detected_buildpack_guid, :environment_json, :memory, :instances, :disk_quota,
- :state, :version, :command, :console, :debug, :staging_task_id,
- :package_state, :health_check_type, :health_check_timeout, :health_check_http_endpoint,
- :staging_failed_reason, :staging_failed_description, :diego, :docker_image, :package_updated_at,
- :detected_start_command, :enable_ssh, :ports
+ :detected_buildpack, :detected_buildpack_guid, :environment_json,
+ :memory, :instances, :disk_quota, :log_rate_limit, :state, :version, :command,
+ :console, :debug, :staging_task_id, :package_state, :health_check_type,
+ :health_check_timeout, :health_check_http_endpoint, :staging_failed_reason,
+ :staging_failed_description, :diego, :docker_image, :package_updated_at,
+ :detected_start_command, :enable_ssh, :ports
import_attributes :name, :production, :space_guid, :stack_guid, :buildpack,
:detected_buildpack, :environment_json, :memory, :instances, :disk_quota,
- :state, :command, :console, :debug, :staging_task_id,
- :service_binding_guids, :route_guids, :health_check_type, :health_check_http_endpoint,
- :health_check_timeout, :diego, :docker_image, :app_guid, :enable_ssh, :ports
+ :log_rate_limit, :state, :command, :console, :debug, :staging_task_id,
+ :service_binding_guids, :route_guids, :health_check_type,
+ :health_check_http_endpoint, :health_check_timeout, :diego,
+ :docker_image, :app_guid, :enable_ssh, :ports
serialize_attributes :json, :metadata
serialize_attributes :integer_array, :ports
@@ -258,6 +261,9 @@ def validation_policies
InstancesPolicy.new(self),
MaxAppInstancesPolicy.new(self, organization, organization && organization.quota_definition, :app_instance_limit_exceeded),
MaxAppInstancesPolicy.new(self, space, space && space.space_quota_definition, :space_app_instance_limit_exceeded),
+ MinLogRateLimitPolicy.new(self),
+ AppMaxLogRateLimitPolicy.new(self, space, 'exceeds space log rate quota'),
+ AppMaxLogRateLimitPolicy.new(self, organization, 'exceeds organization log rate quota'),
HealthCheckPolicy.new(self, health_check_timeout, health_check_invocation_timeout),
DockerPolicy.new(self),
PortsPolicy.new(self)
@@ -373,10 +379,6 @@ def being_stopped?
column_changed?(:state) && stopped?
end
- def scaling_operation?
- started?
- end
-
def desired_instances
started? ? instances : 0
end
diff --git a/app/models/runtime/quota_definition.rb b/app/models/runtime/quota_definition.rb
index 81374dda85d..b4d24cae2c2 100644
--- a/app/models/runtime/quota_definition.rb
+++ b/app/models/runtime/quota_definition.rb
@@ -12,12 +12,13 @@ class QuotaDefinition < Sequel::Model
export_attributes :name, :non_basic_services_allowed, :total_services, :total_routes,
:total_private_domains, :memory_limit, :trial_db_allowed, :instance_memory_limit,
- :app_instance_limit, :app_task_limit, :total_service_keys, :total_reserved_route_ports
+ :app_instance_limit, :app_task_limit, :total_service_keys, :total_reserved_route_ports,
+ :log_rate_limit
import_attributes :name, :non_basic_services_allowed, :total_services, :total_routes,
:total_private_domains, :memory_limit, :trial_db_allowed, :instance_memory_limit,
- :app_instance_limit, :app_task_limit, :total_service_keys, :total_reserved_route_ports
+ :app_instance_limit, :app_task_limit, :total_service_keys, :total_reserved_route_ports,
+ :log_rate_limit
- # rubocop:disable Metrics/CyclomaticComplexity
def validate
validates_presence :name
validates_unique :name
@@ -27,14 +28,14 @@ def validate
validates_presence :memory_limit
validate_total_reserved_route_ports
- errors.add(:memory_limit, :invalid_memory_limit) if memory_limit && memory_limit < UNLIMITED
- errors.add(:instance_memory_limit, :invalid_instance_memory_limit) if instance_memory_limit && instance_memory_limit < UNLIMITED
- errors.add(:total_private_domains, :invalid_total_private_domains) if total_private_domains && total_private_domains < UNLIMITED
- errors.add(:app_instance_limit, :invalid_app_instance_limit) if app_instance_limit && app_instance_limit < UNLIMITED
- errors.add(:app_task_limit, :invalid_app_task_limit) if app_task_limit && app_task_limit < UNLIMITED
- errors.add(:total_service_keys, :invalid_total_service_keys) if total_service_keys && total_service_keys < UNLIMITED
+ validates_limit(:memory_limit, memory_limit)
+ validates_limit(:instance_memory_limit, instance_memory_limit)
+ validates_limit(:total_private_domains, total_private_domains)
+ validates_limit(:app_instance_limit, app_instance_limit)
+ validates_limit(:app_task_limit, app_task_limit)
+ validates_limit(:log_rate_limit, log_rate_limit)
+ validates_limit(:total_service_keys, total_service_keys)
end
- # rubocop:enable Metrics/CyclomaticComplexity
def before_destroy
if organizations.present?
@@ -66,6 +67,10 @@ def self.user_visibility_filter(user)
private
+ def validates_limit(limit_name, limit)
+ errors.add(limit_name, :"invalid_#{limit_name}") if limit && limit < UNLIMITED
+ end
+
def validate_total_reserved_route_ports
return unless total_reserved_route_ports
diff --git a/app/models/runtime/space.rb b/app/models/runtime/space.rb
index a7566a84f9d..65daf1b7fb6 100644
--- a/app/models/runtime/space.rb
+++ b/app/models/runtime/space.rb
@@ -286,6 +286,12 @@ def has_remaining_memory(mem)
space_quota_definition.memory_limit == SpaceQuotaDefinition::UNLIMITED || memory_remaining >= mem
end
+ def has_remaining_log_rate_limit(log_rate_limit_desired)
+ return true unless space_quota_definition
+
+ space_quota_definition.log_rate_limit == SpaceQuotaDefinition::UNLIMITED || log_rate_limit_remaining >= log_rate_limit_desired
+ end
+
def instance_memory_limit
if space_quota_definition
space_quota_definition.instance_memory_limit
@@ -294,6 +300,14 @@ def instance_memory_limit
end
end
+ def log_rate_limit
+ if space_quota_definition
+ space_quota_definition.log_rate_limit
+ else
+ SpaceQuotaDefinition::UNLIMITED
+ end
+ end
+
def app_task_limit
if space_quota_definition
space_quota_definition.app_task_limit
@@ -329,6 +343,10 @@ def memory_remaining
space_quota_definition.memory_limit - memory_used
end
+ def log_rate_limit_remaining
+ space_quota_definition.log_rate_limit - (started_app_log_rate_limit + running_task_log_rate_limit)
+ end
+
def running_task_memory
tasks_dataset.where(state: TaskModel::RUNNING_STATE).sum(:memory_in_mb) || 0
end
@@ -337,6 +355,14 @@ def started_app_memory
processes_dataset.where(state: ProcessModel::STARTED).sum(Sequel.*(:memory, :instances)) || 0
end
+ def running_task_log_rate_limit
+ tasks_dataset.where(state: TaskModel::RUNNING_STATE).sum(:log_rate_limit) || 0
+ end
+
+ def started_app_log_rate_limit
+ processes_dataset.where(state: ProcessModel::STARTED).sum(Sequel.*(:log_rate_limit, :instances)) || 0
+ end
+
def running_and_pending_tasks_count
tasks_dataset.where(state: [TaskModel::PENDING_STATE, TaskModel::RUNNING_STATE]).count
end
diff --git a/app/models/runtime/space_quota_definition.rb b/app/models/runtime/space_quota_definition.rb
index 4be195c6a4d..bb0668cc42d 100644
--- a/app/models/runtime/space_quota_definition.rb
+++ b/app/models/runtime/space_quota_definition.rb
@@ -18,10 +18,10 @@ class SpaceQuotaDefinition < Sequel::Model
export_attributes :name, :organization_guid, :non_basic_services_allowed, :total_services,
:total_routes, :memory_limit, :instance_memory_limit, :app_instance_limit, :app_task_limit,
- :total_service_keys, :total_reserved_route_ports
+ :total_service_keys, :total_reserved_route_ports, :log_rate_limit
import_attributes :name, :organization_guid, :non_basic_services_allowed, :total_services,
:total_routes, :memory_limit, :instance_memory_limit, :app_instance_limit, :app_task_limit,
- :total_service_keys, :total_reserved_route_ports
+ :total_service_keys, :total_reserved_route_ports, :log_rate_limit
add_association_dependencies spaces: :nullify
@@ -34,11 +34,13 @@ def validate
validates_presence :organization
validates_unique [:organization_id, :name]
- errors.add(:memory_limit, :invalid_memory_limit) if memory_limit && memory_limit < UNLIMITED
- errors.add(:instance_memory_limit, :invalid_instance_memory_limit) if instance_memory_limit && instance_memory_limit < -1
- errors.add(:app_instance_limit, :invalid_app_instance_limit) if app_instance_limit && app_instance_limit < UNLIMITED
- errors.add(:app_task_limit, :invalid_app_task_limit) if app_task_limit && app_task_limit < UNLIMITED
- errors.add(:total_service_keys, :invalid_total_service_keys) if total_service_keys && total_service_keys < UNLIMITED
+ validates_limit(:memory_limit, memory_limit)
+ validates_limit(:instance_memory_limit, instance_memory_limit)
+ validates_limit(:app_instance_limit, app_instance_limit)
+ validates_limit(:app_task_limit, app_task_limit)
+ validates_limit(:log_rate_limit, log_rate_limit)
+ validates_limit(:total_service_keys, total_service_keys)
+
validate_total_reserved_ports
end
@@ -58,6 +60,10 @@ def self.user_visibility_filter(user)
private
+ def validates_limit(limit_name, limit)
+ errors.add(limit_name, :"invalid_#{limit_name}") if limit && limit < UNLIMITED
+ end
+
def validate_total_reserved_ports
return unless total_reserved_route_ports
diff --git a/app/models/runtime/task_model.rb b/app/models/runtime/task_model.rb
index c3541bb9bdd..be692b0f5c2 100644
--- a/app/models/runtime/task_model.rb
+++ b/app/models/runtime/task_model.rb
@@ -67,17 +67,21 @@ def validate
validates_presence :name
validate_org_quotas
validate_space_quotas
+
+ MinLogRateLimitPolicy.new(self).validate
end
def validate_space_quotas
TaskMaxMemoryPolicy.new(self, space, 'exceeds space memory quota').validate
TaskMaxInstanceMemoryPolicy.new(self, space, 'exceeds space instance memory quota').validate
+ TaskMaxLogRateLimitPolicy.new(self, space, 'exceeds space log rate quota').validate
new? && MaxAppTasksPolicy.new(self, space, 'quota exceeded').validate
end
def validate_org_quotas
TaskMaxMemoryPolicy.new(self, organization, 'exceeds organization memory quota').validate
TaskMaxInstanceMemoryPolicy.new(self, organization, 'exceeds organization instance memory quota').validate
+ TaskMaxLogRateLimitPolicy.new(self, organization, 'exceeds organization log rate quota').validate
new? && MaxAppTasksPolicy.new(self, organization, 'quota exceeded').validate
end
diff --git a/app/presenters/v2/process_model_presenter.rb b/app/presenters/v2/process_model_presenter.rb
index 956ff1b3b77..a87330d54d5 100644
--- a/app/presenters/v2/process_model_presenter.rb
+++ b/app/presenters/v2/process_model_presenter.rb
@@ -21,6 +21,7 @@ def entity_hash(controller, process, opts, depth, parents, orphans=nil)
'memory' => process.memory,
'instances' => process.instances,
'disk_quota' => process.disk_quota,
+ 'log_rate_limit' => process.log_rate_limit,
'state' => process.state,
'version' => process.version,
'command' => process.command.presence,
diff --git a/app/presenters/v3/app_manifest_presenters/process_properties_presenter.rb b/app/presenters/v3/app_manifest_presenters/process_properties_presenter.rb
index 0535df9a8e5..904e5a8a9db 100644
--- a/app/presenters/v3/app_manifest_presenters/process_properties_presenter.rb
+++ b/app/presenters/v3/app_manifest_presenters/process_properties_presenter.rb
@@ -14,6 +14,7 @@ def process_hash(process)
'instances' => process.instances,
'memory' => add_units(process.memory),
'disk_quota' => add_units(process.disk_quota),
+ 'log-rate-limit-per-second' => add_units_log_rate_limit(process.log_rate_limit),
'command' => process.command,
'health-check-type' => process.health_check_type,
'health-check-http-endpoint' => process.health_check_http_endpoint,
@@ -24,6 +25,16 @@ def process_hash(process)
def add_units(val)
"#{val}M"
end
+
+ def add_units_log_rate_limit(val)
+ return -1 if val == -1
+
+ byte_converter.human_readable_byte_value(val)
+ end
+
+ def byte_converter
+ ByteConverter.new
+ end
end
end
end
diff --git a/app/presenters/v3/build_presenter.rb b/app/presenters/v3/build_presenter.rb
index 855349f0fe7..4676bf39772 100644
--- a/app/presenters/v3/build_presenter.rb
+++ b/app/presenters/v3/build_presenter.rb
@@ -22,6 +22,7 @@ def to_hash
state: build.state,
staging_memory_in_mb: build.staging_memory_in_mb,
staging_disk_in_mb: build.staging_disk_in_mb,
+ staging_log_rate_limit_bytes_per_second: build.staging_log_rate_limit,
error: error,
lifecycle: {
type: build.lifecycle_type,
diff --git a/app/presenters/v3/organization_quota_presenter.rb b/app/presenters/v3/organization_quota_presenter.rb
index 5f0d1593a65..f4c15b04434 100644
--- a/app/presenters/v3/organization_quota_presenter.rb
+++ b/app/presenters/v3/organization_quota_presenter.rb
@@ -26,6 +26,7 @@ def to_hash
per_process_memory_in_mb: convert_unlimited_to_nil(organization_quota.instance_memory_limit),
total_instances: convert_unlimited_to_nil(organization_quota.app_instance_limit),
per_app_tasks: convert_unlimited_to_nil(organization_quota.app_task_limit),
+ log_rate_limit_in_bytes_per_second: convert_unlimited_to_nil(organization_quota.log_rate_limit),
},
services: {
paid_services_allowed: organization_quota.non_basic_services_allowed,
diff --git a/app/presenters/v3/process_presenter.rb b/app/presenters/v3/process_presenter.rb
index 26d0f7fb2ce..d68eb4f5c5c 100644
--- a/app/presenters/v3/process_presenter.rb
+++ b/app/presenters/v3/process_presenter.rb
@@ -19,14 +19,15 @@ def to_hash
health_check_data = { timeout: process.health_check_timeout, invocation_timeout: process.health_check_invocation_timeout }
health_check_data[:endpoint] = process.health_check_http_endpoint if process.health_check_type == HealthCheckTypes::HTTP
{
- guid: process.guid,
- created_at: process.created_at,
- updated_at: process.updated_at,
- type: process.type,
- command: redact(process.specified_or_detected_command),
- instances: process.instances,
- memory_in_mb: process.memory,
- disk_in_mb: process.disk_quota,
+ guid: process.guid,
+ created_at: process.created_at,
+ updated_at: process.updated_at,
+ type: process.type,
+ command: redact(process.specified_or_detected_command),
+ instances: process.instances,
+ memory_in_mb: process.memory,
+ disk_in_mb: process.disk_quota,
+ log_rate_limit_in_bytes_per_second: process.log_rate_limit,
health_check: {
type: process.health_check_type,
data: health_check_data
diff --git a/app/presenters/v3/process_stats_presenter.rb b/app/presenters/v3/process_stats_presenter.rb
index 47fbc14802d..4c17c083c62 100644
--- a/app/presenters/v3/process_stats_presenter.rb
+++ b/app/presenters/v3/process_stats_presenter.rb
@@ -39,6 +39,7 @@ def found_instance_stats_hash(index, stats)
uptime: stats[:stats][:uptime],
mem_quota: stats[:stats][:mem_quota],
disk_quota: stats[:stats][:disk_quota],
+ log_rate_limit: stats[:stats][:log_rate_limit],
fds_quota: stats[:stats][:fds_quota],
isolation_segment: stats[:isolation_segment],
details: stats[:details]
@@ -74,6 +75,7 @@ def add_usage_info(presented_stats, stats)
cpu: stats[:stats][:usage][:cpu],
mem: stats[:stats][:usage][:mem],
disk: stats[:stats][:usage][:disk],
+ log_rate: stats[:stats][:usage][:log_rate],
}
else
{}
diff --git a/app/presenters/v3/space_quota_presenter.rb b/app/presenters/v3/space_quota_presenter.rb
index 1c1b3ab2a8d..4d4da24c28e 100644
--- a/app/presenters/v3/space_quota_presenter.rb
+++ b/app/presenters/v3/space_quota_presenter.rb
@@ -26,6 +26,7 @@ def to_hash
per_process_memory_in_mb: unlimited_to_nil(space_quota.instance_memory_limit),
total_instances: unlimited_to_nil(space_quota.app_instance_limit),
per_app_tasks: unlimited_to_nil(space_quota.app_task_limit),
+ log_rate_limit_in_bytes_per_second: unlimited_to_nil(space_quota.log_rate_limit),
},
services: {
paid_services_allowed: space_quota.non_basic_services_allowed,
diff --git a/app/presenters/v3/task_presenter.rb b/app/presenters/v3/task_presenter.rb
index 616a9ec99b6..2ba114fc33a 100644
--- a/app/presenters/v3/task_presenter.rb
+++ b/app/presenters/v3/task_presenter.rb
@@ -18,6 +18,7 @@ def to_hash
state: task.state,
memory_in_mb: task.memory_in_mb,
disk_in_mb: task.disk_in_mb,
+ log_rate_limit_in_bytes_per_second: task.log_rate_limit,
result: { failure_reason: task.failure_reason },
droplet_guid: task.droplet_guid,
relationships: { app: { data: { guid: task.app_guid } } },
diff --git a/config/bosh-lite.yml b/config/bosh-lite.yml
index 3931c6bec2a..c2c3f001251 100644
--- a/config/bosh-lite.yml
+++ b/config/bosh-lite.yml
@@ -36,6 +36,7 @@ completed_tasks:
default_app_memory: 256
default_app_disk_in_mb: 1024
+default_app_log_rate_limit_in_bytes_per_second: 1_048_576
maximum_app_disk_in_mb: 2048
instance_file_descriptor_limit: 16384
diff --git a/config/cloud_controller.yml b/config/cloud_controller.yml
index eb3e61c0249..008f76b658a 100644
--- a/config/cloud_controller.yml
+++ b/config/cloud_controller.yml
@@ -13,7 +13,6 @@ readiness_port:
external_protocol: http
external_domain: api2.vcap.me
temporary_disable_deployments: true
-temporary_use_logcache: false
deployment_updater:
update_frequency_in_seconds: 30
lock_key: 'cf-deployment-updater'
@@ -53,6 +52,7 @@ completed_tasks:
default_app_memory: 1024 #mb
default_app_disk_in_mb: 1024
+default_app_log_rate_limit_in_bytes_per_second: 1_048_576
maximum_app_disk_in_mb: 2048
max_retained_deployments_per_app: 100
max_retained_builds_per_app: 100
diff --git a/db/migrations/20220822224900_add_log_rate_limit.rb b/db/migrations/20220822224900_add_log_rate_limit.rb
new file mode 100644
index 00000000000..4045a7388fb
--- /dev/null
+++ b/db/migrations/20220822224900_add_log_rate_limit.rb
@@ -0,0 +1,9 @@
+Sequel.migration do
+ change do
+ add_column :quota_definitions, :log_rate_limit, :Bignum, null: false, default: -1
+ add_column :space_quota_definitions, :log_rate_limit, :Bignum, null: false, default: -1
+ add_column :processes, :log_rate_limit, :Bignum, null: false, default: -1
+ add_column :tasks, :log_rate_limit, :Bignum, null: false, default: -1
+ add_column :builds, :staging_log_rate_limit, :Bignum, null: false, default: -1
+ end
+end
diff --git a/docs/v3/source/includes/api_resources/_builds.erb b/docs/v3/source/includes/api_resources/_builds.erb
index 478847084f6..5c77c4af345 100644
--- a/docs/v3/source/includes/api_resources/_builds.erb
+++ b/docs/v3/source/includes/api_resources/_builds.erb
@@ -25,6 +25,7 @@
"state": "STAGING",
"staging_memory_in_mb": 1024,
"staging_disk_in_mb": 1024,
+ "staging_log_rate_limit_bytes_per_second": 1024,
"error": null,
"lifecycle": {
"type": "buildpack",
@@ -74,6 +75,7 @@
"state": "STAGING",
"staging_memory_in_mb": 1024,
"staging_disk_in_mb": 1024,
+ "staging_log_rate_limit_bytes_per_second": 1024,
"error": null,
"lifecycle": {
"type": "buildpack",
@@ -121,6 +123,7 @@
"state": "STAGED",
"staging_memory_in_mb": 1024,
"staging_disk_in_mb": 1024,
+ "staging_log_rate_limit_bytes_per_second": 1024,
"error": null,
"lifecycle": {
"type": "buildpack",
diff --git a/docs/v3/source/includes/api_resources/_organization_quotas.erb b/docs/v3/source/includes/api_resources/_organization_quotas.erb
index 80c9a0ac3fe..e1bf9bc058b 100644
--- a/docs/v3/source/includes/api_resources/_organization_quotas.erb
+++ b/docs/v3/source/includes/api_resources/_organization_quotas.erb
@@ -7,6 +7,7 @@
"apps": {
"total_memory_in_mb": 5120,
"per_process_memory_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"total_instances": 10,
"per_app_tasks": 5
},
@@ -71,6 +72,7 @@
"apps": {
"total_memory_in_mb": 5120,
"per_process_memory_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"total_instances": 10,
"per_app_tasks": 5
},
@@ -105,6 +107,7 @@
"apps": {
"total_memory_in_mb": 2048,
"per_process_memory_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"total_instances": 5,
"per_app_tasks": 2
},
diff --git a/docs/v3/source/includes/api_resources/_processes.erb b/docs/v3/source/includes/api_resources/_processes.erb
index 2a3eddd8ff5..b2eb729391a 100644
--- a/docs/v3/source/includes/api_resources/_processes.erb
+++ b/docs/v3/source/includes/api_resources/_processes.erb
@@ -6,6 +6,7 @@
"instances": 5,
"memory_in_mb": 256,
"disk_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"health_check": {
"type": "port",
"data": {
@@ -75,6 +76,7 @@
"instances": 5,
"memory_in_mb": 256,
"disk_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"health_check": {
"type": "port",
"data": {
@@ -125,6 +127,7 @@
"instances": 1,
"memory_in_mb": 256,
"disk_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"health_check": {
"type": "process",
"data": {
diff --git a/docs/v3/source/includes/api_resources/_space_quotas.erb b/docs/v3/source/includes/api_resources/_space_quotas.erb
index a0172bac79e..4b15d082d5d 100644
--- a/docs/v3/source/includes/api_resources/_space_quotas.erb
+++ b/docs/v3/source/includes/api_resources/_space_quotas.erb
@@ -7,6 +7,7 @@
"apps": {
"total_memory_in_mb": 5120,
"per_process_memory_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"total_instances": 10,
"per_app_tasks": null
},
@@ -67,6 +68,7 @@
"apps": {
"total_memory_in_mb": 5120,
"per_process_memory_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"total_instances": 10,
"per_app_tasks": null
},
@@ -110,6 +112,7 @@
"apps": {
"total_memory_in_mb": 2048,
"per_process_memory_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"total_instances": 5,
"per_app_tasks": 2
},
diff --git a/docs/v3/source/includes/api_resources/_tasks.erb b/docs/v3/source/includes/api_resources/_tasks.erb
index 189e4e0fd3c..33dda9c7723 100644
--- a/docs/v3/source/includes/api_resources/_tasks.erb
+++ b/docs/v3/source/includes/api_resources/_tasks.erb
@@ -7,6 +7,7 @@
"state": "RUNNING",
"memory_in_mb": 512,
"disk_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"result": {
"failure_reason": null
},
@@ -51,6 +52,7 @@
"state": "CANCELING",
"memory_in_mb": 512,
"disk_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"result": {
"failure_reason": null
},
@@ -110,6 +112,7 @@
"state": "SUCCEEDED",
"memory_in_mb": 512,
"disk_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"result": {
"failure_reason": null
},
@@ -150,6 +153,7 @@
"state": "FAILED",
"memory_in_mb": 512,
"disk_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"result": {
"failure_reason": "Exited with status 1"
},
diff --git a/docs/v3/source/includes/resources/builds/_create.md.erb b/docs/v3/source/includes/resources/builds/_create.md.erb
index 49e8673cc40..1ff9e360122 100644
--- a/docs/v3/source/includes/resources/builds/_create.md.erb
+++ b/docs/v3/source/includes/resources/builds/_create.md.erb
@@ -43,6 +43,7 @@ Name | Type | Description | Default
**lifecycle** | [_lifecycle object_](#the-lifecycle-object) | Lifecycle information for a build | lifecycle on the app
**staging_memory_in_mb** | _integer_ | Memory in MB allocated for staging of the build
**staging_disk_in_mb** | _integer_ | Disk space in MB allocated for staging of the build
+**staging_log_rate_limit_bytes_per_second** | _integer_ | Log rate limit in bytes per second allocated for staging of the build
**metadata.labels** | [_label object_](#labels) | Labels applied to the build
**metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the build
diff --git a/docs/v3/source/includes/resources/builds/_object.md.erb b/docs/v3/source/includes/resources/builds/_object.md.erb
index 381cfea2808..aee39c46375 100644
--- a/docs/v3/source/includes/resources/builds/_object.md.erb
+++ b/docs/v3/source/includes/resources/builds/_object.md.erb
@@ -15,6 +15,7 @@ Name | Type | Description
**state** | _string_ | State of the build; valid states are `STAGING`, `STAGED`, or `FAILED`
**staging_memory_in_mb** | _integer_ | Memory in MB allocated for staging of the build
**staging_disk_in_mb** | _integer_ | Disk space in MB allocated for staging of the build
+**staging_log_rate_limit_bytes_per_second** | _integer_ | Log rate limit in bytes per second allocated for staging of the build
**error** | _string_ | A string describing errors during the build process
**lifecycle** | [_lifecycle object_](#the-lifecycle-object) | Provides the lifecycle object to use during staging; this will override the build's application's default lifecycle for this build
**package.guid** | _string_ | The package that is the input to the staging process
diff --git a/docs/v3/source/includes/resources/feature_flags/_flags.md.erb b/docs/v3/source/includes/resources/feature_flags/_flags.md.erb
index 4f0de9eb32a..2222d010adc 100644
--- a/docs/v3/source/includes/resources/feature_flags/_flags.md.erb
+++ b/docs/v3/source/includes/resources/feature_flags/_flags.md.erb
@@ -1,7 +1,7 @@
### List of feature flags
<% feature_flags = [
["app_bits_upload", "true", "When enabled, space developers can upload app bits. When disabled, only admin users can upload app bits."],
-["app_scaling", "true", "When enabled, space developers can perform scaling operations (i.e. change memory, disk or instances). When disabled, only admins can perform scaling operations."],
+["app_scaling", "true", "When enabled, space developers can perform scaling operations (i.e. change memory, disk, log rate, or instances). When disabled, only admins can perform scaling operations."],
["diego_docker", "false", "When enabled, Docker applications are supported by Diego. When disabled, Docker applications will stop running. It will still be possible to stop and delete them and update their configurations."],
["env_var_visibility", "true", "When enabled, all users can see their environment variables. When disabled, no users can see environment variables."],
["hide_marketplace_from_unauthenticated_users", "false", "When enabled, service offerings available in the marketplace will be hidden from unauthenticated users. When disabled, unauthenticated users will be able to see the service offerings available in the marketplace."],
diff --git a/docs/v3/source/includes/resources/manifests/_get.md b/docs/v3/source/includes/resources/manifests/_get.md
index d6eea6f0a81..fb3cdfec81d 100644
--- a/docs/v3/source/includes/resources/manifests/_get.md
+++ b/docs/v3/source/includes/resources/manifests/_get.md
@@ -31,6 +31,7 @@ applications:
- type: web
instances: 2
memory: 512M
+ log-rate-limit-per-second: 1KB
disk_quota: 1024M
health-check-type: port
```
diff --git a/docs/v3/source/includes/resources/manifests/_object.md.erb b/docs/v3/source/includes/resources/manifests/_object.md.erb
index 7245878ead0..2cccf51a136 100644
--- a/docs/v3/source/includes/resources/manifests/_object.md.erb
+++ b/docs/v3/source/includes/resources/manifests/_object.md.erb
@@ -46,6 +46,7 @@ applications:
health-check-invocation-timeout: 10
instances: 3
memory: 500M
+ log-rate-limit-per-second: 1KB
timeout: 10
- type: worker
command: start-worker.sh
@@ -53,6 +54,7 @@ applications:
health-check-type: process
instances: 2
memory: 256M
+ log-rate-limit-per-second: 1KB
timeout: 15
- name: app2
env:
@@ -61,6 +63,7 @@ applications:
- type: web
instances: 1
memory: 256M
+ log-rate-limit-per-second: 1KB
sidecars:
- name: authenticator
process_types: [ 'web', 'worker' ]
@@ -117,6 +120,7 @@ Name | Type | Description
**health-check-type** | _string_ | Type of health check to perform; `none` is deprecated and an alias to `process`
**instances** | _integer_ | The number of instances to run
**memory** | _string_ | The memory limit for all instances of the web process;
this attribute requires a unit of measurement: `B`, `K`, `KB`, `M`, `MB`, `G`, `GB`, `T`, or `TB` in upper case or lower case
+**log-rate-limit-per-second** | _string_ | The log rate limit for all the instances of the process;
this attribute requires a unit of measurement: `B`, `K`, `KB`, `M`, `MB`, `G`, `GB`, `T`, or `TB` in upper case or lower case, or -1 or 0
**timeout** | _integer_ | Time in seconds at which the health-check will report failure
#### Route-level configuration
diff --git a/docs/v3/source/includes/resources/organization_quotas/_create.md.erb b/docs/v3/source/includes/resources/organization_quotas/_create.md.erb
index c578c4a05c2..eb07babdcf6 100644
--- a/docs/v3/source/includes/resources/organization_quotas/_create.md.erb
+++ b/docs/v3/source/includes/resources/organization_quotas/_create.md.erb
@@ -46,6 +46,7 @@ Name | Type | Description | Default |
| **apps.per_process_memory_in_mb** | _integer_ or `null` | Maximum memory for a single process or task | null (infinite) |
| **apps.total_memory_in_mb** | _integer_ or `null` | Total memory allowed for all the started processes and running tasks in an organization | null (infinite) |
| **apps.total_instances** | _integer_ or `null` | Total instances of all the started processes allowed in an organization | null (infinite) |
+| **apps.log_rate_limit_in_bytes_per_second** | _integer_ or `null` | Total log rate limit allowed for all the started processes and running tasks in an organization | null (infinite) |
| **apps.per_app_tasks** | _integer_ or `null` | Maximum number of running tasks in an organization | null (infinite) |
| **services** | _object_ | Quotas that affect services |
| **services.paid_services_allowed** | _boolean_ | Specifies whether instances of paid service plans can be created | true |
diff --git a/docs/v3/source/includes/resources/organization_quotas/_header.md b/docs/v3/source/includes/resources/organization_quotas/_header.md
index 4388905386c..7f644d0931f 100644
--- a/docs/v3/source/includes/resources/organization_quotas/_header.md
+++ b/docs/v3/source/includes/resources/organization_quotas/_header.md
@@ -1,6 +1,6 @@
## Organization Quotas
-Organization quotas are named sets of memory, service, and instance usage quotas. For example, one organization quota might allow up to 10 services, 10 routes, and 2 GB of RAM, while another might offer 100 services, 100 routes, and 10 GB of RAM.
+Organization quotas are named sets of memory, log rate, service, and instance usage quotas. For example, one organization quota might allow up to 10 services, 10 routes, and 2 GB of RAM, while another might offer 100 services, 100 routes, and 10 GB of RAM.
An organization has exactly one organization quota. If not specifically assigned a quota, it will have the default quota.
diff --git a/docs/v3/source/includes/resources/organization_quotas/_object.md.erb b/docs/v3/source/includes/resources/organization_quotas/_object.md.erb
index be670c1f2d7..3274bc010f3 100644
--- a/docs/v3/source/includes/resources/organization_quotas/_object.md.erb
+++ b/docs/v3/source/includes/resources/organization_quotas/_object.md.erb
@@ -17,6 +17,7 @@ Name | Type | Description|
| **apps.per_process_memory_in_mb** | _integer_ or `null` | Maximum memory for a single process or task |
| **apps.total_memory_in_mb** | _integer_ or `null` | Total memory allowed for all the started processes and running tasks in an organization |
| **apps.total_instances** | _integer_ or `null` | Total instances of all the started processes allowed in an organization |
+| **apps.log_rate_limit_in_bytes_per_second** | _integer_ or `null` | Total log rate limit allowed for all the started processes and running tasks in an organization | null (infinite) |
| **apps.per_app_tasks** | _integer_ or `null` | Maximum number of running tasks in an organization |
| **services** | _object_ | Quotas that affect services |
| **services.paid_services_allowed** | _boolean_ | Specifies whether instances of paid service plans can be created |
diff --git a/docs/v3/source/includes/resources/organization_quotas/_update.md.erb b/docs/v3/source/includes/resources/organization_quotas/_update.md.erb
index 8338b4f509b..93ff5e14e6c 100644
--- a/docs/v3/source/includes/resources/organization_quotas/_update.md.erb
+++ b/docs/v3/source/includes/resources/organization_quotas/_update.md.erb
@@ -14,6 +14,7 @@ curl "https://api.example.org/v3/organization_quotas/[guid]" \
"apps": {
"total_memory_in_mb": 5120,
"per_process_memory_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"total_instances": 10,
"per_app_tasks": 5
},
@@ -56,6 +57,7 @@ Name | Type | Description
| **apps.per_process_memory_in_mb** | _integer_ or `null` | Maximum memory for a single process or task
| **apps.total_memory_in_mb** | _integer_ or `null` | Total memory of all the started processes and running tasks in an organization
| **apps.total_instances** | _integer_ or `null` | Total instances of all the started processes in an organization
+| **apps.log_rate_limit_in_bytes_per_second** | _integer_ or `null` | Total log rate limit allowed for all the started processes and running tasks in an organization | null (infinite) |
| **apps.per_app_tasks** | _integer_ or `null` | Maximum number of running tasks in an organization
| **services** | _object_ | Quotas that affect services
| **services.paid_services_allowed** | _boolean_ | If instances of paid service plans can be created
diff --git a/docs/v3/source/includes/resources/processes/_object.md.erb b/docs/v3/source/includes/resources/processes/_object.md.erb
index 3d07fbc1f34..bcb24be3926 100644
--- a/docs/v3/source/includes/resources/processes/_object.md.erb
+++ b/docs/v3/source/includes/resources/processes/_object.md.erb
@@ -16,6 +16,7 @@ Name | Type | Description
**command** | _string_ or _null_ | The command used to start the process; use _null_ to revert to the buildpack-detected or procfile-provided start command
**instances** | _integer_ | The number of instances to run
**memory_in_mb** | _integer_ | The memory in MB allocated per instance
+**log_rate_limit_in_bytes_per_second** | _integer_ | The log rate in bytes per second allocated per instance
**disk_in_mb** | _integer_ | The disk in MB allocated per instance
**health_check** | [_health check object_](#the-health-check-object) | The health check to perform on the process
**relationships.app** | [_to-one relationship_](#to-one-relationships) | The app the process belongs to
diff --git a/docs/v3/source/includes/resources/processes/_scale.md.erb b/docs/v3/source/includes/resources/processes/_scale.md.erb
index 59ae2b7fb83..1bbd7f45a4c 100644
--- a/docs/v3/source/includes/resources/processes/_scale.md.erb
+++ b/docs/v3/source/includes/resources/processes/_scale.md.erb
@@ -12,7 +12,8 @@ curl "https://api.example.org/v3/processes/[guid]/actions/scale" \
-d '{
"instances": 5,
"memory_in_mb": 256,
- "disk_in_mb": 1024
+ "disk_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024
}'
```
@@ -38,6 +39,7 @@ Name | Type | Description
**instances** | _integer_ | The number of instances to run
**memory_in_mb** | _integer_ | The memory in mb allocated per instance
**disk_in_mb** | _integer_ | The disk in mb allocated per instance
+**log_rate_limit_in_bytes_per_second** | _integer_ | The log rate in bytes per second allocated per instance
#### Permitted roles
|
diff --git a/docs/v3/source/includes/resources/processes/_stats_object.md.erb b/docs/v3/source/includes/resources/processes/_stats_object.md.erb
index c72a8729954..db3ba33c14e 100644
--- a/docs/v3/source/includes/resources/processes/_stats_object.md.erb
+++ b/docs/v3/source/includes/resources/processes/_stats_object.md.erb
@@ -17,11 +17,13 @@ Name | Type | Description
**usage.cpu** | _number_ | The current cpu usage of the instance
**usage.mem** | _integer_ | The current memory usage of the instance
**usage.disk** | _integer_ | The current disk usage of the instance
+**usage.log_rate** | _integer_ | The current logging usage of the instance
**host** | _string_ | The host the instance is running on
**instance_ports** | _object_ | JSON array of port mappings between the network-exposed port used to communicate with the app (`external`) and port opened to the running process that it can listen on (`internal`)
**uptime** | _integer_ | The uptime in seconds for the instance
**mem_quota** | _integer_ | The current maximum memory allocated for the instance; the value is `null` when memory quota data is unavailable
**disk_quota** | _integer_ | The current maximum disk allocated for the instance; the value is `null` when disk quota data is unavailable
+**log_rate_limit** | _integer_ | The current maximum log rate allocated for the instance; the value `-1` is unlimited, the value is `null` when the log_rate_limit is unavailable
**fds_quota** | _integer_ | The maximum file descriptors the instance is allowed to use
**isolation_segment** | _string_ | The current isolation segment that the instance is running on; the value is `null` when the instance is not placed on a particular isolation segment
**details** | _string_ | Information about errors placing the instance; the value is `null` if there are no placement errors
diff --git a/docs/v3/source/includes/resources/space_quotas/_create.md.erb b/docs/v3/source/includes/resources/space_quotas/_create.md.erb
index 66fdab82dae..4952def69cc 100644
--- a/docs/v3/source/includes/resources/space_quotas/_create.md.erb
+++ b/docs/v3/source/includes/resources/space_quotas/_create.md.erb
@@ -51,6 +51,7 @@ Name | Type | Description | Default |
| **apps.per_process_memory_in_mb** | _integer_ or `null` | Maximum memory for a single process or task | null (infinite) |
| **apps.total_memory_in_mb** | _integer_ or `null` | Total memory allowed for all the started processes and running tasks in a space | null (infinite) |
| **apps.total_instances** | _integer_ or `null` | Total instances of all the started processes allowed in a space | null (infinite) |
+| **apps.log_rate_limit_in_bytes_per_second** | _integer_ or `null` | Total log rate limit allowed for all the started processes and running tasks in an organization | null (infinite) |
| **apps.per_app_tasks** | _integer_ or `null` | Maximum number of running tasks in a space | null (infinite) |
| **services** | _object_ | Quotas that affect services |
| **services.paid_services_allowed** | _boolean_ | Specifies whether instances of paid service plans can be created | true |
diff --git a/docs/v3/source/includes/resources/space_quotas/_object.md.erb b/docs/v3/source/includes/resources/space_quotas/_object.md.erb
index 28a8735b602..6f9b4a451e8 100644
--- a/docs/v3/source/includes/resources/space_quotas/_object.md.erb
+++ b/docs/v3/source/includes/resources/space_quotas/_object.md.erb
@@ -17,6 +17,7 @@ Name | Type | Description|
| **apps.per_process_memory_in_mb** | _integer_ or `null` | Maximum memory for a single process or task |
| **apps.total_memory_in_mb** | _integer_ or `null` | Total memory allowed for all the started processes and running tasks in a space |
| **apps.total_instances** | _integer_ or `null` | Total instances of all the started processes allowed in a space |
+| **apps.log_rate_limit_in_bytes_per_second** | _integer_ or `null` | Total log rate limit allowed for all the started processes and running tasks in an organization | null (infinite) |
| **apps.per_app_tasks** | _integer_ or `null` | Maximum number of running tasks in a space |
| **services** | _object_ | Quotas that affect services |
| **services.paid_services_allowed** | _boolean_ | Specifies whether instances of paid service plans can be created |
diff --git a/docs/v3/source/includes/resources/space_quotas/_update.md.erb b/docs/v3/source/includes/resources/space_quotas/_update.md.erb
index 876b2eac29b..fab9b05dda2 100644
--- a/docs/v3/source/includes/resources/space_quotas/_update.md.erb
+++ b/docs/v3/source/includes/resources/space_quotas/_update.md.erb
@@ -14,6 +14,7 @@ curl "https://api.example.org/v3/space_quotas/[guid]" \
"apps": {
"total_memory_in_mb": 5120,
"per_process_memory_in_mb": 1024,
+ "log_rate_limit_in_bytes_per_second": 1024,
"total_instances": 10,
"per_app_tasks": 5
},
@@ -53,6 +54,7 @@ Name | Type | Description
| **apps.per_process_memory_in_mb** | _integer_ or `null` | Maximum memory for a single process or task
| **apps.total_memory_in_mb** | _integer_ or `null` | Total memory of all the started processes and running tasks in a space
| **apps.total_instances** | _integer_ or `null` | Total instances of all the started processes in a space
+| **apps.log_rate_limit_in_bytes_per_second** | _integer_ or `null` | Total log rate limit allowed for all the started processes and running tasks in an organization | null (infinite) |
| **apps.per_app_tasks** | _integer_ or `null` | Maximum number of running tasks in a space
| **services** | _object_ | Quotas that affect services
| **services.paid_services_allowed** | _boolean_ | If instances of paid service plans can be created
diff --git a/docs/v3/source/includes/resources/tasks/_create.md.erb b/docs/v3/source/includes/resources/tasks/_create.md.erb
index cb0f9b35355..4366a275152 100644
--- a/docs/v3/source/includes/resources/tasks/_create.md.erb
+++ b/docs/v3/source/includes/resources/tasks/_create.md.erb
@@ -51,6 +51,7 @@ Name | Type | Description | Default
**name** | _string_ | Name of the task | auto-generated
**disk_in_mb**[1] | _integer_ | Amount of disk to allocate for the task in MB | operator-configured `default_app_disk_in_mb`
**memory_in_mb**[1] | _integer_ | Amount of memory to allocate for the task in MB | operator-configured `default_app_memory`
+**log_rate_limit_per_second**[1] | _integer_ | Amount of log rate to allocate for the task in bytes | operator-configured `default_app_log_rate_limit_in_bytes_per_second`
**droplet_guid** | _uuid_ | The guid of the droplet that will be used to run the command | the app's current droplet
**template.process.guid** | _uuid_ | The guid of the process that will be used as a template | `null`
**metadata.labels** | [_label object_](#labels) | Labels applied to the package
diff --git a/docs/v3/source/includes/resources/tasks/_object.md.erb b/docs/v3/source/includes/resources/tasks/_object.md.erb
index cc6ba76a623..6d5a9340c13 100644
--- a/docs/v3/source/includes/resources/tasks/_object.md.erb
+++ b/docs/v3/source/includes/resources/tasks/_object.md.erb
@@ -18,6 +18,7 @@ Name | Type | Description
**state** | _string_ | State of the task Possible states are `PENDING`, `RUNNING`, `SUCCEEDED`, `CANCELING`, and `FAILED`
**memory_in_mb** | _integer_ | Amount of memory to allocate for the task in MB
**disk_in_mb** | _integer_ | Amount of disk to allocate for the task in MB
+**log_rate_limit_per_second** | _integer_ | Amount of log rate to allocate for the task in bytes
**result** | _object_ | Results from the task
**result.failure_reason** | _string_ | Null if the task succeeds, contains the error message if it fails
**droplet_guid** | _uuid_ | The guid of the droplet that will be used to run the command
diff --git a/errors/v2.yml b/errors/v2.yml
index 48d3f2058f9..3e18ca1c9bc 100644
--- a/errors/v2.yml
+++ b/errors/v2.yml
@@ -473,6 +473,11 @@
http_code: 400
message: "The requested memory allocation is not large enough to run all of your sidecar processes."
+100010:
+ name: OrgQuotaLogRateLimitExceeded
+ http_code: 400
+ message: "You have exceeded your organization's log rate limit: %s"
+
110001:
name: ServicePlanInvalid
http_code: 400
@@ -1118,6 +1123,11 @@
http_code: 400
message: "You have exceeded the total reserved route ports for your space's quota."
+310011:
+ name: SpaceQuotaLogRateLimitExceeded
+ http_code: 400
+ message: "You have exceeded your space's log rate limit: %s"
+
320001:
name: DiegoDisabled
http_code: 400
diff --git a/lib/cloud_controller/app_manifest/byte_converter.rb b/lib/cloud_controller/app_manifest/byte_converter.rb
index e5c2828083b..1dbf589367e 100644
--- a/lib/cloud_controller/app_manifest/byte_converter.rb
+++ b/lib/cloud_controller/app_manifest/byte_converter.rb
@@ -2,6 +2,7 @@
module VCAP::CloudController
class ByteConverter
+ class InvalidBytesError < StandardError; end
class InvalidUnitsError < StandardError; end
class NonNumericError < StandardError; end
@@ -15,5 +16,34 @@ def convert_to_mb(human_readable_byte_value)
rescue PalmCivet::InvalidByteQuantityError
raise InvalidUnitsError
end
+
+ def convert_to_b(human_readable_byte_value)
+ return nil unless human_readable_byte_value.present?
+ if !human_readable_byte_value.to_s.match?(/\A-?\d+(?:\.\d+)?/)
+ raise NonNumericError
+ end
+
+ PalmCivet.to_bytes(human_readable_byte_value.to_s)
+ rescue PalmCivet::InvalidByteQuantityError
+ raise InvalidUnitsError
+ end
+
+ def human_readable_byte_value(bytes)
+ return nil unless bytes.present?
+
+ if !bytes.is_a?(Integer)
+ raise InvalidBytesError
+ end
+
+ units = %w(B K M G T)
+ while units.any?
+ unit_in_bytes = 1024**(units.length - 1)
+ if bytes.remainder(unit_in_bytes).zero?
+ return "#{bytes / unit_in_bytes}#{units.last}"
+ end
+
+ units.pop
+ end
+ end
end
end
diff --git a/lib/cloud_controller/app_services/process_log_rate_limit_calculator.rb b/lib/cloud_controller/app_services/process_log_rate_limit_calculator.rb
new file mode 100644
index 00000000000..eae51fec2d2
--- /dev/null
+++ b/lib/cloud_controller/app_services/process_log_rate_limit_calculator.rb
@@ -0,0 +1,51 @@
+module VCAP::CloudController
+ class ProcessLogRateLimitCalculator
+ attr_reader :process
+
+ def initialize(process)
+ @process = process
+ end
+
+ def additional_log_rate_limit_requested
+ return 0 if process.stopped?
+ return QuotaDefinition::UNLIMITED if is_process_log_rate_unlimited?
+
+ total_requested_log_rate_limit - currently_used_log_rate_limit
+ end
+
+ def total_requested_log_rate_limit
+ return 0 if process.log_rate_limit == QuotaDefinition::UNLIMITED
+
+ process.log_rate_limit * process.instances
+ end
+
+ def currently_used_log_rate_limit
+ return 0 if process.new?
+
+ db_process = process_from_db
+ return 0 if db_process.stopped? || db_process[:log_rate_limit] == QuotaDefinition::UNLIMITED
+
+ db_process[:log_rate_limit] * db_process[:instances]
+ end
+
+ private
+
+ def is_process_log_rate_unlimited?
+ process.log_rate_limit == QuotaDefinition::UNLIMITED
+ end
+
+ def process_from_db
+ error_message = 'Expected process record not found in database with guid %s'
+ process_fetched_from_db = ProcessModel.find(guid: process.guid)
+ if process_fetched_from_db.nil?
+ logger.fatal('process.find.missing', guid: process.guid, self: process.inspect)
+ raise CloudController::Errors::ApplicationMissing.new(sprintf(error_message, guid: process.guid))
+ end
+ process_fetched_from_db
+ end
+
+ def logger
+ @logger ||= Steno.logger('cc.process_log_rate_limit_calculator')
+ end
+ end
+end
diff --git a/lib/cloud_controller/backends/instances_reporters.rb b/lib/cloud_controller/backends/instances_reporters.rb
index f39328d2c02..5199fee7cdf 100644
--- a/lib/cloud_controller/backends/instances_reporters.rb
+++ b/lib/cloud_controller/backends/instances_reporters.rb
@@ -38,12 +38,7 @@ def diego_reporter
end
def diego_stats_reporter
- client = if Config.config.get(:temporary_use_logcache)
- dependency_locator.traffic_controller_compatible_logcache_client
- else
- dependency_locator.traffic_controller_client
- end
- Diego::InstancesStatsReporter.new(dependency_locator.bbs_instances_client, client)
+ Diego::InstancesStatsReporter.new(dependency_locator.bbs_instances_client, dependency_locator.log_cache_metrics_client)
end
def dependency_locator
diff --git a/lib/cloud_controller/backends/quota_validating_staging_log_rate_limit_calculator.rb b/lib/cloud_controller/backends/quota_validating_staging_log_rate_limit_calculator.rb
new file mode 100644
index 00000000000..a6ec550bfd2
--- /dev/null
+++ b/lib/cloud_controller/backends/quota_validating_staging_log_rate_limit_calculator.rb
@@ -0,0 +1,28 @@
+module VCAP::CloudController
+ class QuotaValidatingStagingLogRateLimitCalculator
+ class SpaceQuotaExceeded < StandardError; end
+ class OrgQuotaExceeded < StandardError; end
+
+ def get_limit(requested_limit, space, org)
+ if requested_limit.nil?
+ requested_limit = -1
+ end
+
+ requested_limit = requested_limit.to_i
+
+ space_quota_exceeded!(requested_limit) unless space.has_remaining_log_rate_limit(requested_limit)
+ org_quota_exceeded!(requested_limit) unless org.has_remaining_log_rate_limit(requested_limit)
+ requested_limit
+ end
+
+ private
+
+ def org_quota_exceeded!(staging_log_rate_limit)
+ raise OrgQuotaExceeded.new("staging requires #{staging_log_rate_limit} bytes per second")
+ end
+
+ def space_quota_exceeded!(staging_log_rate_limit)
+ raise SpaceQuotaExceeded.new("staging requires #{staging_log_rate_limit} bytes per second")
+ end
+ end
+end
diff --git a/lib/cloud_controller/config_schemas/base/api_schema.rb b/lib/cloud_controller/config_schemas/base/api_schema.rb
index 3a8d6cb5850..a60c56e8380 100644
--- a/lib/cloud_controller/config_schemas/base/api_schema.rb
+++ b/lib/cloud_controller/config_schemas/base/api_schema.rb
@@ -11,7 +11,6 @@ class ApiSchema < VCAP::Config
external_port: Integer,
external_domain: String,
temporary_disable_deployments: bool,
- temporary_use_logcache: bool,
optional(:temporary_disable_v2_staging) => bool,
tls_port: Integer,
external_protocol: String,
@@ -38,6 +37,7 @@ class ApiSchema < VCAP::Config
default_app_memory: Integer,
default_app_disk_in_mb: Integer,
+ default_app_log_rate_limit_in_bytes_per_second: Integer,
maximum_app_disk_in_mb: Integer,
default_health_check_timeout: Integer,
maximum_health_check_timeout: Integer,
diff --git a/lib/cloud_controller/config_schemas/base/clock_schema.rb b/lib/cloud_controller/config_schemas/base/clock_schema.rb
index 92f50b6a54e..10f14a576cf 100644
--- a/lib/cloud_controller/config_schemas/base/clock_schema.rb
+++ b/lib/cloud_controller/config_schemas/base/clock_schema.rb
@@ -155,6 +155,7 @@ class ClockSchema < VCAP::Config
default_app_memory: Integer,
default_app_disk_in_mb: Integer,
+ default_app_log_rate_limit_in_bytes_per_second: Integer,
instance_file_descriptor_limit: Integer,
maximum_app_disk_in_mb: Integer,
max_retained_deployments_per_app: Integer,
diff --git a/lib/cloud_controller/config_schemas/base/worker_schema.rb b/lib/cloud_controller/config_schemas/base/worker_schema.rb
index ceb64038f3c..dadb554bde2 100644
--- a/lib/cloud_controller/config_schemas/base/worker_schema.rb
+++ b/lib/cloud_controller/config_schemas/base/worker_schema.rb
@@ -166,6 +166,7 @@ class WorkerSchema < VCAP::Config
default_app_disk_in_mb: Integer,
instance_file_descriptor_limit: Integer,
maximum_app_disk_in_mb: Integer,
+ default_app_log_rate_limit_in_bytes_per_second: Integer,
default_app_ssh_access: bool,
jobs: {
diff --git a/lib/cloud_controller/dependency_locator.rb b/lib/cloud_controller/dependency_locator.rb
index db2e77cd4e6..3b031d2e57c 100644
--- a/lib/cloud_controller/dependency_locator.rb
+++ b/lib/cloud_controller/dependency_locator.rb
@@ -12,9 +12,8 @@
require 'cloud_controller/blob_sender/nginx_blob_sender'
require 'cloud_controller/blob_sender/default_blob_sender'
require 'cloud_controller/blob_sender/missing_blob_handler'
-require 'traffic_controller/client'
require 'logcache/client'
-require 'logcache/traffic_controller_decorator'
+require 'logcache/container_metric_batcher'
require 'cloud_controller/diego/task_recipe_builder'
require 'cloud_controller/diego/app_recipe_builder'
require 'cloud_controller/diego/bbs_apps_client'
@@ -102,17 +101,13 @@ def bbs_instances_client
@dependencies[:bbs_instances_client] || register(:bbs_instances_client, build_instances_client)
end
- def traffic_controller_client
- @dependencies[:traffic_controller_client] || register(:traffic_controller_client, build_traffic_controller_client)
- end
-
def logcache_client
@dependencies[:logcache_client] || register(:logcache_client, build_logcache_client)
end
- def traffic_controller_compatible_logcache_client
- @dependencies[:traffic_controller_compatible_logcache_client] ||
- register(:traffic_controller_compatible_logcache_client, Logcache::TrafficControllerDecorator.new(logcache_client))
+ def log_cache_metrics_client
+ @dependencies[:log_cache_metrics_client] ||
+ register(:log_cache_metrics_client, Logcache::ContainerMetricBatcher.new(logcache_client))
end
def upload_handler
@@ -516,10 +511,6 @@ def build_bbs_client
)
end
- def build_traffic_controller_client
- TrafficController::Client.new(url: config.get(:loggregator, :internal_url))
- end
-
def build_logcache_client
Logcache::Client.new(
host: config.get(:logcache, :host),
diff --git a/lib/cloud_controller/diego/app_recipe_builder.rb b/lib/cloud_controller/diego/app_recipe_builder.rb
index 1ccec7e8d6d..cb5add91d94 100644
--- a/lib/cloud_controller/diego/app_recipe_builder.rb
+++ b/lib/cloud_controller/diego/app_recipe_builder.rb
@@ -68,6 +68,7 @@ def app_lrp_arguments
start_timeout_ms: health_check_timeout_in_seconds * 1000,
disk_mb: process.disk_quota,
memory_mb: process.memory, # sums up
+ log_rate_limit: ::Diego::Bbs::Models::LogRateLimit.new(bytes_per_second: process.log_rate_limit),
privileged: desired_lrp_builder.privileged?,
ports: ports,
log_source: LRP_LOG_SOURCE,
diff --git a/lib/cloud_controller/diego/reporters/instances_stats_reporter.rb b/lib/cloud_controller/diego/reporters/instances_stats_reporter.rb
index a37352776b6..2fe140f1aef 100644
--- a/lib/cloud_controller/diego/reporters/instances_stats_reporter.rb
+++ b/lib/cloud_controller/diego/reporters/instances_stats_reporter.rb
@@ -1,4 +1,3 @@
-require 'traffic_controller/client'
require 'logcache/client'
require 'cloud_controller/diego/reporters/reporter_mixins'
@@ -70,14 +69,16 @@ def stats_for_app(process)
def metrics_data_for_instance(stats, quota_stats, log_cache_errors, formatted_current_time, index)
if log_cache_errors.blank?
{
- mem_quota: quota_stats[index]&.memoryBytesQuota,
- disk_quota: quota_stats[index]&.diskBytesQuota,
+ mem_quota: quota_stats[index]&.memory_bytes_quota,
+ disk_quota: quota_stats[index]&.disk_bytes_quota,
+ log_rate_limit: quota_stats[index]&.log_rate_limit,
usage: stats[index] || missing_process_stats(formatted_current_time)
}
else
{
mem_quota: nil,
disk_quota: nil,
+ log_rate_limit: nil,
usage: {}
}
end
@@ -89,6 +90,7 @@ def missing_process_stats(formatted_current_time)
cpu: 0,
mem: 0,
disk: 0,
+ log_rate: 0,
}
end
@@ -96,8 +98,8 @@ def formatted_process_stats(log_cache_data, formatted_current_time)
log_cache_data.
map { |e|
[
- e.containerMetric.instanceIndex,
- converted_container_metrics(e.containerMetric, formatted_current_time),
+ e.instance_index,
+ converted_container_metrics(e, formatted_current_time),
]
}.to_h
end
@@ -106,8 +108,8 @@ def formatted_quota_stats(log_cache_data)
log_cache_data.
map { |e|
[
- e.containerMetric.instanceIndex,
- e.containerMetric,
+ e.instance_index,
+ e
]
}.to_h
end
@@ -136,18 +138,20 @@ def logger
end
def converted_container_metrics(container_metrics, formatted_current_time)
- cpu = container_metrics.cpuPercentage
- mem = container_metrics.memoryBytes
- disk = container_metrics.diskBytes
+ cpu = container_metrics.cpu_percentage
+ mem = container_metrics.memory_bytes
+ disk = container_metrics.disk_bytes
+ log_rate = container_metrics.log_rate
- if cpu.nil? || mem.nil? || disk.nil?
+ if cpu.nil? || mem.nil? || disk.nil? || log_rate.nil?
missing_process_stats(formatted_current_time)
else
{
time: formatted_current_time,
cpu: cpu / 100,
mem: mem,
- disk: disk
+ disk: disk,
+ log_rate: log_rate
}
end
end
diff --git a/lib/cloud_controller/diego/staging_details.rb b/lib/cloud_controller/diego/staging_details.rb
index 152fa322980..51018788ac4 100644
--- a/lib/cloud_controller/diego/staging_details.rb
+++ b/lib/cloud_controller/diego/staging_details.rb
@@ -1,7 +1,7 @@
module VCAP::CloudController
module Diego
class StagingDetails
- attr_accessor :staging_guid, :staging_memory_in_mb, :staging_disk_in_mb, :package,
+ attr_accessor :staging_guid, :staging_memory_in_mb, :staging_disk_in_mb, :staging_log_rate_limit_bytes_per_second, :package,
:environment_variables, :lifecycle, :start_after_staging, :isolation_segment
end
end
diff --git a/lib/cloud_controller/diego/task_recipe_builder.rb b/lib/cloud_controller/diego/task_recipe_builder.rb
index ba9ae460ca9..a73c71400d1 100644
--- a/lib/cloud_controller/diego/task_recipe_builder.rb
+++ b/lib/cloud_controller/diego/task_recipe_builder.rb
@@ -26,6 +26,7 @@ def build_app_task(config, task)
disk_mb: task.disk_in_mb,
egress_rules: @egress_rules.running_protobuf_rules(task.app),
log_guid: task.app.guid,
+ log_rate_limit: ::Diego::Bbs::Models::LogRateLimit.new(bytes_per_second: task.log_rate_limit),
log_source: TASK_LOG_SOURCE,
max_pids: config.get(:diego, :pid_limit),
memory_mb: task.memory_in_mb,
@@ -64,6 +65,7 @@ def build_staging_task(config, staging_details)
log_guid: staging_details.package.app_guid,
log_source: STAGING_LOG_SOURCE,
memory_mb: staging_details.staging_memory_in_mb,
+ log_rate_limit: ::Diego::Bbs::Models::LogRateLimit.new(bytes_per_second: staging_details.staging_log_rate_limit_bytes_per_second),
network: generate_network(staging_details.package, Protocol::ContainerNetworkInfo::STAGING),
privileged: config.get(:diego, :use_privileged_containers_for_staging),
result_file: STAGING_RESULT_FILE,
diff --git a/lib/logcache/container_metric_batch.rb b/lib/logcache/container_metric_batch.rb
new file mode 100644
index 00000000000..a3f4cf8bc6b
--- /dev/null
+++ b/lib/logcache/container_metric_batch.rb
@@ -0,0 +1,7 @@
+module Logcache
+ class ContainerMetricBatch
+ attr_accessor :cpu_percentage, :memory_bytes, :disk_bytes, :log_rate,
+ :disk_bytes_quota, :memory_bytes_quota, :log_rate_limit,
+ :instance_index
+ end
+end
diff --git a/lib/logcache/traffic_controller_decorator.rb b/lib/logcache/container_metric_batcher.rb
similarity index 69%
rename from lib/logcache/traffic_controller_decorator.rb
rename to lib/logcache/container_metric_batcher.rb
index d6378992fb8..456572c6df0 100644
--- a/lib/logcache/traffic_controller_decorator.rb
+++ b/lib/logcache/container_metric_batcher.rb
@@ -1,8 +1,9 @@
require 'logcache/client'
require 'utils/time_utils'
+require 'logcache/container_metric_batch'
module Logcache
- class TrafficControllerDecorator
+ class ContainerMetricBatcher
MAX_REQUEST_COUNT = 100
def initialize(logcache_client)
@@ -40,7 +41,7 @@ def container_metrics(auth_token: nil, source_guid:, logcache_filter:)
uniq { |e| e.gauge.metrics.keys << e.instance_id }.
sort_by(&:instance_id).
chunk(&:instance_id).
- map { |envelopes_by_instance| convert_to_traffic_controller_envelope(source_guid, envelopes_by_instance) }
+ map { |envelopes_by_instance| batch_metrics(source_guid, envelopes_by_instance) }
end
private
@@ -66,47 +67,45 @@ def has_container_metrics_fields?(envelope)
envelope.gauge.metrics.has_key?('memory') ||
envelope.gauge.metrics.has_key?('memory_quota') ||
envelope.gauge.metrics.has_key?('disk') ||
- envelope.gauge.metrics.has_key?('disk_quota')
+ envelope.gauge.metrics.has_key?('disk_quota') ||
+ envelope.gauge.metrics.has_key?('log_rate') ||
+ envelope.gauge.metrics.has_key?('log_rate_limit')
# rubocop:enable Style/PreferredHashMethods
end
- def convert_to_traffic_controller_envelope(source_guid, envelopes_by_instance)
- tc_envelope = TrafficController::Models::Envelope.new(
- containerMetric: TrafficController::Models::ContainerMetric.new({
- applicationId: source_guid,
- instanceIndex: envelopes_by_instance.first,
- }),
- )
+ def batch_metrics(source_guid, envelopes_by_instance)
+ metric_batch = ContainerMetricBatch.new
+ metric_batch.instance_index = envelopes_by_instance.first.to_i
- tags = {}
envelopes_by_instance.second.each { |e|
- tc_envelope.containerMetric.instanceIndex = e.instance_id
# rubocop seems to think that there is a 'key?' method
# on envelope.gauge.metrics - but it does not
# rubocop:disable Style/PreferredHashMethods
if e.gauge.metrics.has_key?('cpu')
- tc_envelope.containerMetric.cpuPercentage = e.gauge.metrics['cpu'].value
+ metric_batch.cpu_percentage = e.gauge.metrics['cpu'].value
end
if e.gauge.metrics.has_key?('memory')
- tc_envelope.containerMetric.memoryBytes = e.gauge.metrics['memory'].value
+ metric_batch.memory_bytes = e.gauge.metrics['memory'].value.to_i
end
if e.gauge.metrics.has_key?('disk')
- tc_envelope.containerMetric.diskBytes = e.gauge.metrics['disk'].value
+ metric_batch.disk_bytes = e.gauge.metrics['disk'].value.to_i
+ end
+ if e.gauge.metrics.has_key?('log_rate')
+ metric_batch.log_rate = e.gauge.metrics['log_rate'].value.to_i
end
if e.gauge.metrics.has_key?('disk_quota')
- tc_envelope.containerMetric.diskBytesQuota = e.gauge.metrics['disk_quota'].value
+ metric_batch.disk_bytes_quota = e.gauge.metrics['disk_quota'].value.to_i
end
if e.gauge.metrics.has_key?('memory_quota')
- tc_envelope.containerMetric.memoryBytesQuota = e.gauge.metrics['memory_quota'].value
+ metric_batch.memory_bytes_quota = e.gauge.metrics['memory_quota'].value.to_i
+ end
+ if e.gauge.metrics.has_key?('log_rate_limit')
+ metric_batch.log_rate_limit = e.gauge.metrics['log_rate_limit'].value.to_i
end
# rubocop:enable Style/PreferredHashMethods
-
- tags.merge!(e.tags.to_h)
}
- tc_envelope.tags = tags.map { |k, v| TrafficController::Models::Envelope::TagsEntry.new(key: k, value: v) }
-
- tc_envelope
+ metric_batch
end
def logger
diff --git a/lib/traffic_controller/client.rb b/lib/traffic_controller/client.rb
deleted file mode 100644
index 63c96953a12..00000000000
--- a/lib/traffic_controller/client.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require 'traffic_controller/traffic_controller'
-require 'traffic_controller/errors'
-require 'utils/multipart_parser_wrapper'
-
-module TrafficController
- class Client
- BOUNDARY_REGEXP = /boundary=(.+)/.freeze
-
- def initialize(url:)
- @url = url
- end
-
- def container_metrics(auth_token:, source_guid:, logcache_filter: nil)
- response = with_request_error_handling do
- client.get("/apps/#{source_guid}/containermetrics", nil, { 'Authorization' => auth_token })
- end
-
- validate_status!(response: response, statuses: [200])
-
- envelopes = []
- boundary = extract_boundary!(response.contenttype)
- parser = VCAP::MultipartParserWrapper.new(body: response.body, boundary: boundary)
- until (next_part = parser.next_part).nil?
- envelopes << protobuf_decode!(next_part, Models::Envelope)
- end
- envelopes
- end
-
- def with_request_error_handling(&blk)
- tries ||= 3
- yield
- rescue => e
- retry unless (tries -= 1).zero?
- raise RequestError.new(e.message)
- end
-
- private
-
- attr_reader :url
-
- def extract_boundary!(content_type)
- match_data = BOUNDARY_REGEXP.match(content_type)
- raise ResponseError.new('failed to find multipart boundary in Content-Type header') if match_data.nil?
-
- match_data.captures.first
- end
-
- def validate_status!(response:, statuses:)
- raise ResponseError.new("failed with status: #{response.status}, body: #{response.body}") unless statuses.include?(response.status)
- end
-
- def protobuf_decode!(message, protobuf_decoder)
- protobuf_decoder.decode(message)
- rescue => e
- raise DecodeError.new(e.message)
- end
-
- def client
- @client ||= build_client
- end
-
- def build_client
- client = HTTPClient.new(base_url: url)
- client.connect_timeout = 10
- client.send_timeout = 10
- client.receive_timeout = 10
- client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
- client
- end
- end
-end
diff --git a/lib/traffic_controller/errors.rb b/lib/traffic_controller/errors.rb
deleted file mode 100644
index 0deb07e55f7..00000000000
--- a/lib/traffic_controller/errors.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module TrafficController
- class Error < StandardError
- end
-
- class RequestError < Error
- end
-
- class ResponseError < Error
- end
-
- class DecodeError < Error
- end
-
- class ParseError < Error
- end
-end
diff --git a/lib/traffic_controller/models/envelope.pb.rb b/lib/traffic_controller/models/envelope.pb.rb
deleted file mode 100644
index 76e4a97a053..00000000000
--- a/lib/traffic_controller/models/envelope.pb.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# encoding: utf-8
-
-##
-# This file is auto-generated. DO NOT EDIT!
-#
-require 'protobuf/message'
-
-##
-# Imports
-#
-require 'http.pb'
-require 'log.pb'
-require 'metric.pb'
-require 'error.pb'
-
-module TrafficController
- module Models
- ##
- # Message Classes
- #
- class Envelope < ::Protobuf::Message
- class EventType < ::Protobuf::Enum
- define :HttpStartStop, 4
- define :LogMessage, 5
- define :ValueMetric, 6
- define :CounterEvent, 7
- define :Error, 8
- define :ContainerMetric, 9
- end
-
- class TagsEntry < ::Protobuf::Message; end
- end
-
- ##
- # Message Fields
- #
- class Envelope
- class TagsEntry
- optional :string, :key, 1
- optional :string, :value, 2
- end
-
- required :string, :origin, 1
- required ::TrafficController::Models::Envelope::EventType, :eventType, 2
- optional :int64, :timestamp, 6
- optional :string, :deployment, 13
- optional :string, :job, 14
- optional :string, :index, 15
- optional :string, :ip, 16
- repeated ::TrafficController::Models::Envelope::TagsEntry, :tags, 17
- optional ::TrafficController::Models::HttpStartStop, :httpStartStop, 7
- optional ::TrafficController::Models::LogMessage, :logMessage, 8
- optional ::TrafficController::Models::ValueMetric, :valueMetric, 9
- optional ::TrafficController::Models::CounterEvent, :counterEvent, 10
- optional ::TrafficController::Models::Error, :error, 11
- optional ::TrafficController::Models::ContainerMetric, :containerMetric, 12
- end
- end
-end
diff --git a/lib/traffic_controller/models/error.pb.rb b/lib/traffic_controller/models/error.pb.rb
deleted file mode 100644
index 5caba72c6d0..00000000000
--- a/lib/traffic_controller/models/error.pb.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# encoding: utf-8
-
-##
-# This file is auto-generated. DO NOT EDIT!
-#
-require 'protobuf/message'
-
-module TrafficController
- module Models
- ##
- # Message Classes
- #
- class Error < ::Protobuf::Message; end
-
- ##
- # Message Fields
- #
- class Error
- required :string, :source, 1
- required :int32, :code, 2
- required :string, :message, 3
- end
- end
-end
diff --git a/lib/traffic_controller/models/http.pb.rb b/lib/traffic_controller/models/http.pb.rb
deleted file mode 100644
index 750525f9cf9..00000000000
--- a/lib/traffic_controller/models/http.pb.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# encoding: utf-8
-
-##
-# This file is auto-generated. DO NOT EDIT!
-#
-require 'protobuf/message'
-
-##
-# Imports
-#
-require 'uuid.pb'
-
-module TrafficController
- module Models
- ##
- # Enum Classes
- #
- class PeerType < ::Protobuf::Enum
- define :Client, 1
- define :Server, 2
- end
-
- class Method < ::Protobuf::Enum
- define :GET, 1
- define :POST, 2
- define :PUT, 3
- define :DELETE, 4
- define :HEAD, 5
- define :ACL, 6
- define :BASELINE_CONTROL, 7
- define :BIND, 8
- define :CHECKIN, 9
- define :CHECKOUT, 10
- define :CONNECT, 11
- define :COPY, 12
- define :DEBUG, 13
- define :LABEL, 14
- define :LINK, 15
- define :LOCK, 16
- define :MERGE, 17
- define :MKACTIVITY, 18
- define :MKCALENDAR, 19
- define :MKCOL, 20
- define :MKREDIRECTREF, 21
- define :MKWORKSPACE, 22
- define :MOVE, 23
- define :OPTIONS, 24
- define :ORDERPATCH, 25
- define :PATCH, 26
- define :PRI, 27
- define :PROPFIND, 28
- define :PROPPATCH, 29
- define :REBIND, 30
- define :REPORT, 31
- define :SEARCH, 32
- define :SHOWMETHOD, 33
- define :SPACEJUMP, 34
- define :TEXTSEARCH, 35
- define :TRACE, 36
- define :TRACK, 37
- define :UNBIND, 38
- define :UNCHECKOUT, 39
- define :UNLINK, 40
- define :UNLOCK, 41
- define :UPDATE, 42
- define :UPDATEREDIRECTREF, 43
- define :VERSION_CONTROL, 44
- end
-
- ##
- # Message Classes
- #
- class HttpStartStop < ::Protobuf::Message; end
-
- ##
- # Message Fields
- #
- class HttpStartStop
- required :int64, :startTimestamp, 1
- required :int64, :stopTimestamp, 2
- required ::TrafficController::Models::UUID, :requestId, 3
- required ::TrafficController::Models::PeerType, :peerType, 4
- required ::TrafficController::Models::Method, :method, 5
- required :string, :uri, 6
- required :string, :remoteAddress, 7
- required :string, :userAgent, 8
- required :int32, :statusCode, 9
- required :int64, :contentLength, 10
- optional ::TrafficController::Models::UUID, :applicationId, 12
- optional :int32, :instanceIndex, 13
- optional :string, :instanceId, 14
- repeated :string, :forwarded, 15
- end
- end
-end
diff --git a/lib/traffic_controller/models/log.pb.rb b/lib/traffic_controller/models/log.pb.rb
deleted file mode 100644
index a8d24fb4e44..00000000000
--- a/lib/traffic_controller/models/log.pb.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# encoding: utf-8
-
-##
-# This file is auto-generated. DO NOT EDIT!
-#
-require 'protobuf/message'
-
-module TrafficController
- module Models
- ##
- # Message Classes
- #
- class LogMessage < ::Protobuf::Message
- class MessageType < ::Protobuf::Enum
- define :OUT, 1
- define :ERR, 2
- end
- end
-
- ##
- # Message Fields
- #
- class LogMessage
- required :bytes, :message, 1
- required ::TrafficController::Models::LogMessage::MessageType, :message_type, 2
- required :int64, :timestamp, 3
- optional :string, :app_id, 4
- optional :string, :source_type, 5
- optional :string, :source_instance, 6
- end
- end
-end
diff --git a/lib/traffic_controller/models/metric.pb.rb b/lib/traffic_controller/models/metric.pb.rb
deleted file mode 100644
index e38d531fcc6..00000000000
--- a/lib/traffic_controller/models/metric.pb.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# encoding: utf-8
-
-##
-# This file is auto-generated. DO NOT EDIT!
-#
-require 'protobuf/message'
-
-##
-# Imports
-#
-require 'uuid.pb'
-
-module TrafficController
- module Models
- ##
- # Message Classes
- #
- class ValueMetric < ::Protobuf::Message; end
- class CounterEvent < ::Protobuf::Message; end
- class ContainerMetric < ::Protobuf::Message; end
-
- ##
- # Message Fields
- #
- class ValueMetric
- required :string, :name, 1
- required :double, :value, 2
- required :string, :unit, 3
- end
-
- class CounterEvent
- required :string, :name, 1
- required :uint64, :delta, 2
- optional :uint64, :total, 3
- end
-
- class ContainerMetric
- required :string, :applicationId, 1
- required :int32, :instanceIndex, 2
- required :double, :cpuPercentage, 3
- required :uint64, :memoryBytes, 4
- required :uint64, :diskBytes, 5
- optional :uint64, :memoryBytesQuota, 6
- optional :uint64, :diskBytesQuota, 7
- end
- end
-end
diff --git a/lib/traffic_controller/models/uuid.pb.rb b/lib/traffic_controller/models/uuid.pb.rb
deleted file mode 100644
index 16b1b5a2fff..00000000000
--- a/lib/traffic_controller/models/uuid.pb.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# encoding: utf-8
-
-##
-# This file is auto-generated. DO NOT EDIT!
-#
-require 'protobuf/message'
-
-module TrafficController
- module Models
- ##
- # Message Classes
- #
- class UUID < ::Protobuf::Message; end
-
- ##
- # Message Fields
- #
- class UUID
- required :uint64, :low, 1
- required :uint64, :high, 2
- end
- end
-end
diff --git a/lib/traffic_controller/traffic_controller.rb b/lib/traffic_controller/traffic_controller.rb
deleted file mode 100644
index 60f9159c88f..00000000000
--- a/lib/traffic_controller/traffic_controller.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-$LOAD_PATH.unshift(File.expand_path('models', __dir__))
-
-require 'traffic_controller/models/envelope.pb'
-require 'traffic_controller/models/error.pb'
-require 'traffic_controller/models/http.pb'
-require 'traffic_controller/models/log.pb'
-require 'traffic_controller/models/metric.pb'
-require 'traffic_controller/models/uuid.pb'
diff --git a/lib/utils/multipart_parser_wrapper.rb b/lib/utils/multipart_parser_wrapper.rb
deleted file mode 100644
index 7c03d9787c3..00000000000
--- a/lib/utils/multipart_parser_wrapper.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'multipart_parser/reader'
-
-module VCAP
- class MultipartParserWrapper
- NEW_LINE = "\r\n".freeze
-
- def initialize(body:, boundary:)
- @body = body
- @boundary = boundary
- end
-
- def next_part
- @chunks ||= parse(@body, @boundary)
- @chunks.next
- rescue StopIteration, ParseError
- nil
- end
-
- private
-
- def parse(body, boundary)
- parts = []
-
- reader = ::MultipartParser::Reader.new(boundary)
-
- reader.on_part do |part|
- p = []
-
- part.on_data do |partial_data|
- p << partial_data
- end
-
- parts << p
- end
-
- reader.write body
-
- unless reader.ended?
- raise ParseError.new('truncated multipart message')
- end
-
- parts.map(&:join).to_enum
- end
- end
-end
diff --git a/scripts/cf-release/restore-bosh-lite-cc.sh b/scripts/cf-release/restore-bosh-lite-cc.sh
index 2a1e6aed7ad..99d834d99da 100755
--- a/scripts/cf-release/restore-bosh-lite-cc.sh
+++ b/scripts/cf-release/restore-bosh-lite-cc.sh
@@ -10,4 +10,3 @@ sed "/bbs.service.cf.internal/d" /etc/hosts | sudo tee /etc/hosts > /dev/null
sed "/bits-service.service.cf.internal/d" /etc/hosts | sudo tee /etc/hosts > /dev/null
sed "/bits-service.bosh-lite.com/d" /etc/hosts | sudo tee /etc/hosts > /dev/null
sed "/uaa.service.cf.internal/d" /etc/hosts | sudo tee /etc/hosts > /dev/null
-sed "/loggregator-trafficcontroller.service.cf.internal/d" /etc/hosts | sudo tee /etc/hosts > /dev/null
diff --git a/scripts/cf-release/setup-local-cc.sh b/scripts/cf-release/setup-local-cc.sh
index 55c61aed108..1331ce12da2 100755
--- a/scripts/cf-release/setup-local-cc.sh
+++ b/scripts/cf-release/setup-local-cc.sh
@@ -24,7 +24,3 @@ fi
if ! cat /etc/hosts | grep "uaa.service.cf.internal" > /dev/null; then
echo "10.244.0.134 uaa.service.cf.internal" | sudo tee -a /etc/hosts > /dev/null
fi
-
-if ! cat /etc/hosts | grep "loggregator-trafficcontroller.service.cf.internal" > /dev/null; then
- echo "10.244.0.150 loggregator-trafficcontroller.service.cf.internal" | sudo tee -a /etc/hosts > /dev/null
-fi
diff --git a/scripts/generate-traffic-controller-models.sh b/scripts/generate-traffic-controller-models.sh
deleted file mode 100755
index e7a9bf2b92e..00000000000
--- a/scripts/generate-traffic-controller-models.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env bash
-
-ruby_generated_files_path="src/cloud_controller_ng/lib/traffic_controller/models"
-traffic_controller_proto_path="${GOPATH}/src/dropsonde-protocol/events"
-generated_ruby_destination="${GOPATH}/${ruby_generated_files_path}"
-
-if [[ ! -d $traffic_controller_proto_path ]]; then
- echo "bbs models were not available at ${traffic_controller_proto_path}"
- exit 1
-fi
-
-if [[ ! -d "${generated_ruby_destination}" ]]; then
- echo "directory not available for generated ruby classes at ${generated_ruby_destination}"
- exit 1
-fi
-
-if [[ ! $(protoc --version) ]]; then
- echo "must install protoc"
- exit 1
-fi
-
-# this gem contains the plugin used to generate ruby models that are proto2 compatible
-if [[ ! $(gem list | grep protobuf) ]]; then
- echo "must 'gem install protobuf'"
- exit 1
-fi
-
-pushd "${traffic_controller_proto_path}"
- # the ruby modules are created based on the package name
- sed -i'' -e 's/package events/package TrafficController.models/' ./*.proto
-
- # this is a hack to allow protoc to use a plugin that supports proto2 generation since protoc only supports proto3 by default
- # see: https://github.com/ruby-protobuf/protobuf/issues/341
- protoc --proto_path="${GOPATH}/src":. --plugin="protoc-gen-bob=$(which protoc-gen-ruby)" --bob_out="${generated_ruby_destination}" ./*.proto
- git checkout .
-popd
diff --git a/scripts/short-circuit-cc b/scripts/short-circuit-cc
index 2048080c1b6..47decb8dbdb 100755
--- a/scripts/short-circuit-cc
+++ b/scripts/short-circuit-cc
@@ -45,7 +45,6 @@ blobstore.service.cf.internal \
cc-uploader.service.cf.internal \
cell.service.cf.internal \
cloud-controller-ng.service.cf.internal \
-loggregator-trafficcontroller.service.cf.internal \
sql-db.service.cf.internal \
uaa.service.cf.internal"
ip_to_dns="$(bosh ssh "${BOSH_API_INSTANCE}" -c "for addr in ${internal_hostnames}; do ip=\"\$(dig +short \${addr})\" && echo \"\${ip:-\"UNKNOWN\"} \${addr}\"; done" -r --column=Stdout | cat)"
diff --git a/spec/logcache/traffic_controller_decorator_spec.rb b/spec/logcache/container_metric_batcher_spec.rb
similarity index 75%
rename from spec/logcache/traffic_controller_decorator_spec.rb
rename to spec/logcache/container_metric_batcher_spec.rb
index 1dda6d03234..b95a9676cff 100644
--- a/spec/logcache/traffic_controller_decorator_spec.rb
+++ b/spec/logcache/container_metric_batcher_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-require 'logcache/traffic_controller_decorator'
+require 'logcache/container_metric_batcher'
require 'utils/time_utils'
-RSpec.describe Logcache::TrafficControllerDecorator do
+RSpec.describe Logcache::ContainerMetricBatcher do
subject { described_class.new(wrapped_logcache_client).container_metrics(source_guid: process_guid, logcache_filter: filter) }
let(:wrapped_logcache_client) { instance_double(Logcache::Client, container_metrics: logcache_response) }
@@ -22,12 +22,9 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: cpu_percentage),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 100 * i + 2),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 100 * i + 3),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 100 * i + 4),
}),
instance_id: (offset + i).to_s,
- tags: {
- 'source_id' => process.app.guid,
- 'process_id' => process.guid,
- },
),
Loggregator::V2::Envelope.new(
timestamp: last_timestamp,
@@ -38,17 +35,13 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'container_age' => Loggregator::V2::GaugeValue.new(unit: 'nanoseconds', value: 100 * i + 3),
}),
instance_id: (offset + i).to_s,
- tags: {
- 'source_id' => process.app.guid,
- 'process_id' => process.guid,
- },
)
]
end
Loggregator::V2::EnvelopeBatch.new(batch: batch)
end
- describe 'converting from Logcache to TrafficController' do
+ describe 'batches envelopes' do
let(:filter) { ->(_) { true } }
before do
@@ -73,6 +66,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 11),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 12),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 13),
}),
instance_id: '1'
),
@@ -82,6 +76,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 13),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
}),
instance_id: '2'
),
@@ -91,6 +86,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 9),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 8),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 7),
}),
instance_id: '1'
),
@@ -103,11 +99,11 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
subject
expect(subject).to have(1).items
- expect(subject.first.containerMetric.applicationId).to eq(process_guid)
- expect(subject.first.containerMetric.instanceIndex).to eq(1)
- expect(subject.first.containerMetric.cpuPercentage).to eq(10)
- expect(subject.first.containerMetric.memoryBytes).to eq(11)
- expect(subject.first.containerMetric.diskBytes).to eq(12)
+ expect(subject.first.instance_index).to eq(1)
+ expect(subject.first.cpu_percentage).to eq(10)
+ expect(subject.first.memory_bytes).to eq(11)
+ expect(subject.first.disk_bytes).to eq(12)
+ expect(subject.first.log_rate).to eq(13)
end
end
@@ -138,6 +134,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 11),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 12),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 13),
}),
instance_id: '1'
),
@@ -156,6 +153,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 20),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 21),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 22),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 23),
}),
instance_id: '2'
),
@@ -174,6 +172,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 30),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 31),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 32),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 33),
}),
instance_id: '3'
)
@@ -182,25 +181,25 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
}
let(:num_instances) { 3 }
- it 'returns an array of envelopes, formatted as Traffic Controller would' do
- expect(subject.first.containerMetric.applicationId).to eq(process_guid)
- expect(subject.first.containerMetric.instanceIndex).to eq(1)
- expect(subject.first.containerMetric.cpuPercentage).to eq(10)
- expect(subject.first.containerMetric.memoryBytes).to eq(11)
- expect(subject.first.containerMetric.diskBytes).to eq(12)
-
- expect(subject.second.containerMetric.applicationId).to eq(process_guid)
- expect(subject.second.containerMetric.instanceIndex).to eq(2)
- expect(subject.second.containerMetric.cpuPercentage).to eq(20)
- expect(subject.second.containerMetric.memoryBytes).to eq(21)
- expect(subject.second.containerMetric.diskBytes).to eq(22)
-
- cm = subject[2].containerMetric
- expect(cm.applicationId).to eq(process_guid)
- expect(cm.instanceIndex).to eq(3)
- expect(cm.cpuPercentage).to eq(30)
- expect(cm.memoryBytes).to eq(31)
- expect(cm.diskBytes).to eq(32)
+ it 'returns an array of batched metrics' do
+ expect(subject.first.instance_index).to eq(1)
+ expect(subject.first.cpu_percentage).to eq(10)
+ expect(subject.first.memory_bytes).to eq(11)
+ expect(subject.first.disk_bytes).to eq(12)
+ expect(subject.first.log_rate).to eq(13)
+
+ expect(subject.second.instance_index).to eq(2)
+ expect(subject.second.cpu_percentage).to eq(20)
+ expect(subject.second.memory_bytes).to eq(21)
+ expect(subject.second.disk_bytes).to eq(22)
+ expect(subject.second.log_rate).to eq(23)
+
+ cm = subject[2]
+ expect(cm.instance_index).to eq(3)
+ expect(cm.cpu_percentage).to eq(30)
+ expect(cm.memory_bytes).to eq(31)
+ expect(cm.disk_bytes).to eq(32)
+ expect(cm.log_rate).to eq(33)
end
end
@@ -210,48 +209,28 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
batch: [Loggregator::V2::Envelope.new(
source_id: process_guid,
gauge: Loggregator::V2::Gauge.new(metrics: {
- 'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
- 'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 11),
- 'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 12),
- 'disk_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 24),
- 'memory_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 25),
+ 'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 0.10),
+ 'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 11.0),
+ 'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 12.0),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 13.0),
+ 'disk_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 24.0),
+ 'memory_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 25.0),
+ 'log_rate_limit' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 26.0),
}),
instance_id: '1'
)]
)
}
- it 'returns an array of one envelope, formatted as Traffic Controller would' do
- expect(subject.first.containerMetric.applicationId).to eq(process_guid)
- expect(subject.first.containerMetric.instanceIndex).to eq(1)
- expect(subject.first.containerMetric.cpuPercentage).to eq(10)
- expect(subject.first.containerMetric.memoryBytes).to eq(11)
- expect(subject.first.containerMetric.diskBytes).to eq(12)
- expect(subject.first.containerMetric.diskBytesQuota).to eq(24)
- expect(subject.first.containerMetric.memoryBytesQuota).to eq(25)
- end
- end
-
- context 'when the envelope does not have tags' do
- let(:envelopes) {
- Loggregator::V2::EnvelopeBatch.new(
- batch: [
- Loggregator::V2::Envelope.new(
- source_id: process_guid,
- gauge: Loggregator::V2::Gauge.new(metrics: {
- 'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
- 'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 11),
- 'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 12),
- }),
- instance_id: '1',
- tags: {},
- ),
- ]
- )
- }
-
- it 'returns an envelope with an empty array of tags' do
- expect(subject.first.tags).to be_empty
+ it 'returns an array of one batched envelope' do
+ expect(subject.first.instance_index).to eql(1)
+ expect(subject.first.cpu_percentage).to eql(0.10)
+ expect(subject.first.memory_bytes).to eql(11)
+ expect(subject.first.disk_bytes).to eql(12)
+ expect(subject.first.log_rate).to eql(13)
+ expect(subject.first.disk_bytes_quota).to eql(24)
+ expect(subject.first.memory_bytes_quota).to eql(25)
+ expect(subject.first.log_rate_limit).to eql(26)
end
end
@@ -265,14 +244,12 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 11),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 12),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 13),
'disk_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 24),
- 'memory_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 25),
+ 'memory_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 25),
+ 'log_rate_limit' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 26),
}),
instance_id: '1',
- tags: {
- 'source_id' => process.app.guid,
- 'process_id' => process.guid,
- },
),
Loggregator::V2::Envelope.new(
source_id: process_guid,
@@ -280,14 +257,12 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 20),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 21),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 22),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 23),
'disk_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 34),
'memory_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 35),
+ 'log_rate_limit' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 36),
}),
instance_id: '2',
- tags: {
- 'source_id' => process.app.guid,
- 'process_id' => process.guid,
- },
),
Loggregator::V2::Envelope.new(
source_id: process_guid,
@@ -295,50 +270,46 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 30),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 31),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 32),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 33),
'disk_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 44),
'memory_quota' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 45),
+ 'log_rate_limit' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 46),
}),
instance_id: '3',
- tags: {
- 'source_id' => process.app.guid,
- 'process_id' => process.guid,
- },
)
]
)
}
let(:num_instances) { 3 }
- it 'returns an array of envelopes, formatted as Traffic Controller would' do
- expect(subject.first.containerMetric.applicationId).to eq(process_guid)
- expect(subject.first.containerMetric.instanceIndex).to eq(1)
- expect(subject.first.containerMetric.cpuPercentage).to eq(10)
- expect(subject.first.containerMetric.memoryBytes).to eq(11)
- expect(subject.first.containerMetric.diskBytes).to eq(12)
- expect(subject.first.containerMetric.diskBytesQuota).to eq(24)
- expect(subject.first.containerMetric.memoryBytesQuota).to eq(25)
-
- expect(subject.first.tags[0].key).to eq('source_id')
- expect(subject.first.tags[0].value).to eq(process.app.guid)
- expect(subject.first.tags[1].key).to eq('process_id')
- expect(subject.first.tags[1].value).to eq(process.guid)
-
- expect(subject.second.containerMetric.applicationId).to eq(process_guid)
- expect(subject.second.containerMetric.instanceIndex).to eq(2)
- expect(subject.second.containerMetric.cpuPercentage).to eq(20)
- expect(subject.second.containerMetric.memoryBytes).to eq(21)
- expect(subject.second.containerMetric.diskBytes).to eq(22)
- expect(subject.second.containerMetric.diskBytesQuota).to eq(34)
- expect(subject.second.containerMetric.memoryBytesQuota).to eq(35)
-
- cm = subject[2].containerMetric
- expect(cm.applicationId).to eq(process_guid)
- expect(cm.instanceIndex).to eq(3)
- expect(cm.cpuPercentage).to eq(30)
- expect(cm.memoryBytes).to eq(31)
- expect(cm.diskBytes).to eq(32)
- expect(cm.diskBytesQuota).to eq(44)
- expect(cm.memoryBytesQuota).to eq(45)
+ it 'returns an array of batched metrics' do
+ expect(subject.first.instance_index).to eq(1)
+ expect(subject.first.cpu_percentage).to eq(10)
+ expect(subject.first.memory_bytes).to eq(11)
+ expect(subject.first.disk_bytes).to eq(12)
+ expect(subject.first.log_rate).to eq(13)
+ expect(subject.first.disk_bytes_quota).to eq(24)
+ expect(subject.first.memory_bytes_quota).to eq(25)
+ expect(subject.first.log_rate_limit).to eq(26)
+
+ expect(subject.second.instance_index).to eq(2)
+ expect(subject.second.cpu_percentage).to eq(20)
+ expect(subject.second.memory_bytes).to eq(21)
+ expect(subject.second.disk_bytes).to eq(22)
+ expect(subject.second.log_rate).to eq(23)
+ expect(subject.second.disk_bytes_quota).to eq(34)
+ expect(subject.second.memory_bytes_quota).to eq(35)
+ expect(subject.second.log_rate_limit).to eq(36)
+
+ cm = subject[2]
+ expect(cm.instance_index).to eq(3)
+ expect(cm.cpu_percentage).to eq(30)
+ expect(cm.memory_bytes).to eq(31)
+ expect(cm.disk_bytes).to eq(32)
+ expect(cm.log_rate).to eq(33)
+ expect(cm.disk_bytes_quota).to eq(44)
+ expect(cm.memory_bytes_quota).to eq(45)
+ expect(cm.log_rate_limit).to eq(46)
end
end
@@ -352,6 +323,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 10),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 11),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 12),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 13),
}),
instance_id: '1'
),
@@ -361,6 +333,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 20),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 21),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 22),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 23),
}),
instance_id: '2'
),
@@ -370,6 +343,7 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 30),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 31),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 32),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 33),
}),
instance_id: '1'
)
@@ -380,9 +354,9 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
it 'returns only the newest metric' do
expect(subject.count).to eq(2)
- expect(subject.first.containerMetric.instanceIndex).to eq(1)
- expect(subject.first.containerMetric.cpuPercentage).to eq(10)
- expect(subject.second.containerMetric.instanceIndex).to eq(2)
+ expect(subject.first.instance_index).to eq(1)
+ expect(subject.first.cpu_percentage).to eq(10)
+ expect(subject.second.instance_index).to eq(2)
end
end
@@ -409,8 +383,8 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
Timecop.freeze(call_time) do
expect(subject).to have(1000).items
- expect(subject.first.containerMetric.instanceIndex).to eq(1)
- expect(subject.first.containerMetric.cpuPercentage).to eq(34.0)
+ expect(subject.first.instance_index).to eq(1)
+ expect(subject.first.cpu_percentage).to eq(34.0)
end
end
end
@@ -440,12 +414,20 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
}),
instance_id: '1'
),
+ Loggregator::V2::Envelope.new(
+ source_id: process_guid,
+ gauge: Loggregator::V2::Gauge.new(metrics: {
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 13),
+ }),
+ instance_id: '1'
+ ),
Loggregator::V2::Envelope.new(
source_id: process_guid,
gauge: Loggregator::V2::Gauge.new(metrics: {
'cpu' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 20),
'memory' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 21),
'disk' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 22),
+ 'log_rate' => Loggregator::V2::GaugeValue.new(unit: 'bytes', value: 23),
}),
instance_id: '2'
)
@@ -457,15 +439,17 @@ def generate_batch(size, offset: 0, last_timestamp: TimeUtils.to_nanoseconds(Tim
it 'returns a single envelope per instance' do
expect(subject.count).to eq(2)
- expect(subject.first.containerMetric.instanceIndex).to eq(1)
- expect(subject.first.containerMetric.cpuPercentage).to eq(10)
- expect(subject.first.containerMetric.memoryBytes).to eq(11)
- expect(subject.first.containerMetric.diskBytes).to eq(12)
-
- expect(subject.second.containerMetric.instanceIndex).to eq(2)
- expect(subject.second.containerMetric.cpuPercentage).to eq(20)
- expect(subject.second.containerMetric.memoryBytes).to eq(21)
- expect(subject.second.containerMetric.diskBytes).to eq(22)
+ expect(subject.first.instance_index).to eq(1)
+ expect(subject.first.cpu_percentage).to eq(10)
+ expect(subject.first.memory_bytes).to eq(11)
+ expect(subject.first.disk_bytes).to eq(12)
+ expect(subject.first.log_rate).to eq(13)
+
+ expect(subject.second.instance_index).to eq(2)
+ expect(subject.second.cpu_percentage).to eq(20)
+ expect(subject.second.memory_bytes).to eq(21)
+ expect(subject.second.disk_bytes).to eq(22)
+ expect(subject.second.log_rate).to eq(23)
end
end
diff --git a/spec/request/app_manifests_spec.rb b/spec/request/app_manifests_spec.rb
index 5ccb23e9ca4..14ad274fcb0 100644
--- a/spec/request/app_manifests_spec.rb
+++ b/spec/request/app_manifests_spec.rb
@@ -39,6 +39,7 @@
health_check_type: 'http',
health_check_http_endpoint: '/foobar',
health_check_timeout: 5,
+ log_rate_limit: 1_048_576,
)
end
@@ -107,6 +108,7 @@
'instances' => process.instances,
'memory' => "#{process.memory}M",
'disk_quota' => "#{process.disk_quota}M",
+ 'log-rate-limit-per-second' => '1M',
'health-check-type' => process.health_check_type,
},
{
@@ -114,6 +116,7 @@
'instances' => worker_process.instances,
'memory' => "#{worker_process.memory}M",
'disk_quota' => "#{worker_process.disk_quota}M",
+ 'log-rate-limit-per-second' => '1M',
'command' => worker_process.command,
'health-check-type' => worker_process.health_check_type,
'health-check-http-endpoint' => worker_process.health_check_http_endpoint,
@@ -170,6 +173,7 @@
before do
app_model.update(droplet: droplet)
+ process.update(log_rate_limit: -1)
end
let(:expected_yml_manifest) do
@@ -203,6 +207,7 @@
'instances' => process.instances,
'memory' => "#{process.memory}M",
'disk_quota' => "#{process.disk_quota}M",
+ 'log-rate-limit-per-second' => -1,
'health-check-type' => process.health_check_type,
},
{
@@ -210,6 +215,7 @@
'instances' => worker_process.instances,
'memory' => "#{worker_process.memory}M",
'disk_quota' => "#{worker_process.disk_quota}M",
+ 'log-rate-limit-per-second' => '1M',
'command' => worker_process.command,
'health-check-type' => worker_process.health_check_type,
'health-check-http-endpoint' => worker_process.health_check_http_endpoint,
diff --git a/spec/request/apps_spec.rb b/spec/request/apps_spec.rb
index 831bb07450c..b22a1d3a896 100644
--- a/spec/request/apps_spec.rb
+++ b/spec/request/apps_spec.rb
@@ -1778,6 +1778,7 @@
app: app_model,
staging_memory_in_mb: 123,
staging_disk_in_mb: 456,
+ staging_log_rate_limit: 789,
created_by_user_name: 'bob the builder',
created_by_user_guid: user.guid,
created_by_user_email: 'bob@loblaw.com'
@@ -1789,6 +1790,7 @@
app: app_model,
staging_memory_in_mb: 123,
staging_disk_in_mb: 456,
+ staging_log_rate_limit: 789,
created_at: build.created_at - 1.day,
created_by_user_name: 'bob the builder',
created_by_user_guid: user.guid,
@@ -1874,6 +1876,7 @@
'error' => nil,
'staging_memory_in_mb' => 123,
'staging_disk_in_mb' => 456,
+ 'staging_log_rate_limit_bytes_per_second' => 789,
'lifecycle' => {
'type' => 'buildpack',
'data' => {
@@ -1902,6 +1905,7 @@
'error' => nil,
'staging_memory_in_mb' => 123,
'staging_disk_in_mb' => 456,
+ 'staging_log_rate_limit_bytes_per_second' => 789,
'lifecycle' => {
'type' => 'buildpack',
'data' => {
@@ -2378,6 +2382,152 @@
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
+
+ describe 'limiting the application log rates' do
+ let(:log_rate_limit) { -1 }
+ let(:space_log_rate_limit) { -1 }
+ let(:org_log_rate_limit) { -1 }
+ let(:org_quota_definition) { VCAP::CloudController::QuotaDefinition.make(log_rate_limit: org_log_rate_limit) }
+ let(:org) { VCAP::CloudController::Organization.make(quota_definition: org_quota_definition) }
+ let(:space_quota_definition) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: org, log_rate_limit: space_log_rate_limit) }
+ let(:space) { VCAP::CloudController::Space.make(organization: org, space_quota_definition: space_quota_definition) }
+ let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: log_rate_limit) }
+ let(:app_model) {
+ VCAP::CloudController::AppModel.make(
+ :buildpack,
+ name: 'app-name',
+ space: space,
+ desired_state: 'STOPPED',
+ )
+ }
+ let(:droplet) { VCAP::CloudController::DropletModel.make(app: app_model, process_types: { web: 'webby' }) }
+
+ before do
+ app_model.update(droplet_guid: droplet.guid)
+ end
+
+ describe 'space quotas' do
+ context 'when both the space and the app do not specify a log rate limit' do
+ let(:log_rate_limit) { -1 }
+ let(:space_log_rate_limit) { -1 }
+
+ it 'starts the app successfully' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(200)
+ end
+ end
+
+ context "when the app fits in the space's log rate limit" do
+ let(:log_rate_limit) { 199 }
+ let(:space_log_rate_limit) { 200 }
+
+ it 'starts the app successfully' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(200)
+ end
+ end
+
+ context "when the app's log rate limit is unspecified, but the space specifies a log rate limit" do
+ let(:log_rate_limit) { -1 }
+ let(:space_log_rate_limit) { 200 }
+
+ it 'fails to start the app' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message("log_rate_limit cannot be unlimited in space '#{space.name}'.")
+ end
+ end
+
+ context "when the app's log rate limit is larger than the limit specified by the space" do
+ let(:log_rate_limit) { 201 }
+ let(:space_log_rate_limit) { 200 }
+
+ it 'fails to start the app' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message('log_rate_limit exceeds space log rate quota')
+ end
+ end
+
+ context "when the space's quota is more strict that the org's quota, the space quota controls" do
+ let(:log_rate_limit) { 201 }
+ let(:space_log_rate_limit) { 200 }
+ let(:org_log_rate_limit) { 201 }
+
+ it 'fails to start the app' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message('log_rate_limit exceeds space log rate quota')
+ end
+ end
+ end
+
+ describe 'organization quotas' do
+ context 'when both the org and the app do not specify a log rate limit' do
+ let(:log_rate_limit) { -1 }
+ let(:org_log_rate_limit) { -1 }
+
+ it 'starts the app successfully' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(200)
+ end
+ end
+
+ context "when the app fits in the org's log rate limit" do
+ let(:log_rate_limit) { 199 }
+ let(:org_log_rate_limit) { 200 }
+
+ it 'starts the app successfully' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(200)
+ end
+ end
+
+ context "when the app's log rate limit is unspecified, but the org specifies a log rate limit" do
+ let(:log_rate_limit) { -1 }
+ let(:org_log_rate_limit) { 200 }
+
+ it 'fails to start the app' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message("log_rate_limit cannot be unlimited in organization '#{org.name}'.")
+ end
+ end
+
+ context "when the app's log rate limit is larger than the limit specified by the org" do
+ let(:log_rate_limit) { 201 }
+ let(:org_log_rate_limit) { 200 }
+
+ it 'fails to start the app' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message('log_rate_limit exceeds organization log rate quota')
+ end
+ end
+
+ context "when the org's quota is more strict that the space's quota, the org quota controls" do
+ let(:log_rate_limit) { 201 }
+ let(:space_log_rate_limit) { 202 }
+ let(:org_log_rate_limit) { 200 }
+
+ it 'fails to start the app' do
+ post "/v3/apps/#{app_model.guid}/actions/start", nil, admin_header
+
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message('log_rate_limit exceeds organization log rate quota')
+ end
+ end
+ end
+ end
end
context 'events' do
diff --git a/spec/request/builds_spec.rb b/spec/request/builds_spec.rb
index 219eebbaf60..34d67ee852b 100644
--- a/spec/request/builds_spec.rb
+++ b/spec/request/builds_spec.rb
@@ -78,6 +78,7 @@
'state' => 'STAGING',
'staging_memory_in_mb' => 42,
'staging_disk_in_mb' => 42,
+ 'staging_log_rate_limit_bytes_per_second' => -1,
'metadata' => { 'labels' => { 'release' => 'stable', 'seriouseats.com/potato' => 'mashed' }, 'annotations' => { 'potato' => 'idaho' } },
'error' => nil,
'lifecycle' => {
@@ -347,6 +348,7 @@
created_by_user_email: 'bob@loblaw.com',
staging_memory_in_mb: 123,
staging_disk_in_mb: 456,
+ staging_log_rate_limit: 234
)
end
let!(:second_build) do
@@ -358,7 +360,8 @@
created_by_user_guid: developer.guid,
created_by_user_email: 'bob@loblaw.com',
staging_memory_in_mb: 789,
- staging_disk_in_mb: 12
+ staging_disk_in_mb: 12,
+ staging_log_rate_limit: 345
)
end
let(:package) { VCAP::CloudController::PackageModel.make(app_guid: app_model.guid) }
@@ -468,6 +471,7 @@
'state' => 'STAGED',
'staging_memory_in_mb' => 123,
'staging_disk_in_mb' => 456,
+ 'staging_log_rate_limit_bytes_per_second' => 234,
'error' => nil,
'lifecycle' => {
'type' => 'buildpack',
@@ -496,6 +500,7 @@
'state' => 'STAGED',
'staging_memory_in_mb' => 789,
'staging_disk_in_mb' => 12,
+ 'staging_log_rate_limit_bytes_per_second' => 345,
'error' => nil,
'lifecycle' => {
'type' => 'buildpack',
@@ -557,6 +562,7 @@
app: app_model,
staging_memory_in_mb: 123,
staging_disk_in_mb: 456,
+ staging_log_rate_limit: 789,
created_by_user_name: 'bob the builder',
created_by_user_guid: developer.guid,
created_by_user_email: 'bob@loblaw.com'
@@ -593,6 +599,7 @@
'state' => 'STAGED',
'staging_memory_in_mb' => 123,
'staging_disk_in_mb' => 456,
+ 'staging_log_rate_limit_bytes_per_second' => 789,
'error' => nil,
'lifecycle' => {
'type' => 'buildpack',
diff --git a/spec/request/organization_quotas_spec.rb b/spec/request/organization_quotas_spec.rb
index 6fcbec4699a..e3dcf90c3f2 100644
--- a/spec/request/organization_quotas_spec.rb
+++ b/spec/request/organization_quotas_spec.rb
@@ -35,7 +35,8 @@ module VCAP::CloudController
total_memory_in_mb: nil,
per_process_memory_in_mb: nil,
total_instances: nil,
- per_app_tasks: nil
+ per_app_tasks: nil,
+ log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
@@ -91,7 +92,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: 10,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
@@ -118,7 +120,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: 10,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
@@ -319,7 +322,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: nil,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
@@ -346,7 +350,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: nil,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
@@ -423,6 +428,7 @@ module VCAP::CloudController
expect(org_quota_to_update.reload.app_task_limit).to eq(9)
expect(org_quota_to_update.reload.memory_limit).to eq(-1)
expect(org_quota_to_update.reload.total_services).to eq(14)
+ expect(org_quota_to_update.reload.log_rate_limit).to eq(-1)
expect(org_quota_to_update.reload.non_basic_services_allowed).to be_falsey
end
@@ -434,6 +440,7 @@ module VCAP::CloudController
expect(org_quota_to_update.reload.app_task_limit).to eq(9)
expect(org_quota_to_update.reload.memory_limit).to eq(-1)
expect(org_quota_to_update.reload.total_services).to eq(14)
+ expect(org_quota_to_update.reload.log_rate_limit).to eq(-1)
expect(org_quota_to_update.reload.non_basic_services_allowed).to be_falsey
end
end
@@ -591,7 +598,8 @@ def generate_org_quota_single_response(list_of_orgs)
total_memory_in_mb: 20480,
per_process_memory_in_mb: nil,
total_instances: nil,
- per_app_tasks: nil
+ per_app_tasks: nil,
+ log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
@@ -639,7 +647,8 @@ def generate_default_org_quota_response(global_read)
total_memory_in_mb: 10240,
per_process_memory_in_mb: nil,
total_instances: nil,
- per_app_tasks: nil
+ per_app_tasks: nil,
+ log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
diff --git a/spec/request/processes_spec.rb b/spec/request/processes_spec.rb
index 4ad1144cf99..7a766fce183 100644
--- a/spec/request/processes_spec.rb
+++ b/spec/request/processes_spec.rb
@@ -37,6 +37,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
)
}
@@ -48,6 +49,7 @@
instances: 1,
memory: 100,
disk_quota: 200,
+ log_rate_limit: 400,
command: 'start worker',
)
}
@@ -111,11 +113,12 @@
}
},
},
- 'type' => 'web',
- 'command' => '[PRIVATE DATA HIDDEN IN LISTS]',
- 'instances' => 2,
- 'memory_in_mb' => 1024,
- 'disk_in_mb' => 1024,
+ 'type' => 'web',
+ 'command' => '[PRIVATE DATA HIDDEN IN LISTS]',
+ 'instances' => 2,
+ 'memory_in_mb' => 1024,
+ 'disk_in_mb' => 1024,
+ 'log_rate_limit_in_bytes_per_second' => 1_048_576,
'health_check' => {
'type' => 'port',
'data' => {
@@ -140,11 +143,12 @@
'app' => { 'data' => { 'guid' => app_model.guid } },
'revision' => nil,
},
- 'type' => 'worker',
- 'command' => '[PRIVATE DATA HIDDEN IN LISTS]',
- 'instances' => 1,
- 'memory_in_mb' => 100,
- 'disk_in_mb' => 200,
+ 'type' => 'worker',
+ 'command' => '[PRIVATE DATA HIDDEN IN LISTS]',
+ 'instances' => 1,
+ 'memory_in_mb' => 100,
+ 'disk_in_mb' => 200,
+ 'log_rate_limit_in_bytes_per_second' => 400,
'health_check' => {
'type' => 'port',
'data' => {
@@ -229,6 +233,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
)
}
@@ -270,6 +275,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
)
}
@@ -307,6 +313,7 @@
instances: 3,
memory: 2048,
disk_quota: 2048,
+ log_rate_limit: 2_097_152,
command: 'at ease'
)
end
@@ -383,6 +390,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
)
end
@@ -394,10 +402,11 @@
'app' => { 'data' => { 'guid' => app_model.guid } },
'revision' => { 'data' => { 'guid' => revision.guid } },
},
- 'command' => 'rackup',
- 'instances' => 2,
- 'memory_in_mb' => 1024,
- 'disk_in_mb' => 1024,
+ 'command' => 'rackup',
+ 'instances' => 2,
+ 'memory_in_mb' => 1024,
+ 'disk_in_mb' => 1024,
+ 'log_rate_limit_in_bytes_per_second' => 1_048_576,
'health_check' => {
'type' => 'port',
'data' => {
@@ -494,12 +503,14 @@
uptime: 12345,
mem_quota: process[:memory] * 1024 * 1024,
disk_quota: process[:disk_quota] * 1024 * 1024,
+ log_rate_limit: process[:log_rate_limit],
fds_quota: process.file_descriptors,
usage: {
time: usage_time,
cpu: 80,
mem: 128,
disk: 1024,
+ log_rate: 1024,
}
}
},
@@ -522,6 +533,7 @@
'cpu' => 80,
'mem' => 128,
'disk' => 1024,
+ 'log_rate' => 1024,
},
'host' => 'toast',
'instance_ports' => [
@@ -541,7 +553,8 @@
'uptime' => 12345,
'mem_quota' => 1073741824,
'disk_quota' => 1073741824,
- 'fds_quota' => 16384
+ 'fds_quota' => 16384,
+ 'log_rate_limit' => 1_048_576
}]
}
end
@@ -602,6 +615,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
ports: [4444, 5555],
health_check_type: 'port',
@@ -634,6 +648,7 @@
'instances' => 2,
'memory_in_mb' => 1024,
'disk_in_mb' => 1024,
+ 'log_rate_limit_in_bytes_per_second' => 1_048_576,
'health_check' => {
'type' => 'process',
'data' => {
@@ -748,15 +763,17 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
)
end
let(:scale_request) do
{
- instances: 5,
+ instances: 5,
memory_in_mb: 10,
- disk_in_mb: 20,
+ disk_in_mb: 20,
+ log_rate_limit_in_bytes_per_second: 40,
}
end
@@ -773,6 +790,7 @@
'instances' => 5,
'memory_in_mb' => 10,
'disk_in_mb' => 20,
+ 'log_rate_limit_in_bytes_per_second' => 40,
'health_check' => {
'type' => 'port',
'data' => {
@@ -805,6 +823,7 @@
expect(process.instances).to eq(5)
expect(process.memory).to eq(10)
expect(process.disk_quota).to eq(20)
+ expect(process.log_rate_limit).to eq(40)
events = VCAP::CloudController::Event.where(actor: developer.guid).all
@@ -826,7 +845,8 @@
'request' => {
'instances' => 5,
'memory_in_mb' => 10,
- 'disk_in_mb' => 20
+ 'disk_in_mb' => 20,
+ 'log_rate_limit_in_bytes_per_second' => 40,
}
})
end
@@ -875,6 +895,7 @@
instances: 5,
memory_in_mb: 10,
disk_in_mb: 20,
+ log_rate_limit_in_bytes_per_second: 40,
}
post "/v3/processes/#{process.guid}/actions/scale", scale_request.to_json, headers_for(space_supporter)
@@ -886,6 +907,7 @@
expect(process.instances).to eq(5)
expect(process.memory).to eq(10)
expect(process.disk_quota).to eq(20)
+ expect(process.log_rate_limit).to eq(40)
end
end
@@ -924,6 +946,20 @@
expect(process.memory).to eq(1024)
end
+ it 'returns a helpful error when the log quota is too small' do
+ scale_request = {
+ log_rate_limit_in_bytes_per_second: -2,
+ }
+
+ post "/v3/processes/#{process.guid}/actions/scale", scale_request.to_json, developer_headers
+
+ expect(last_response.status).to eq(422)
+ expect(parsed_response['errors'][0]['detail']).to eq 'Log rate limit in bytes per second must be greater than or equal to -1'
+
+ process.reload
+ expect(process.log_rate_limit).to eq(1_048_576)
+ end
+
context 'telemetry' do
let(:process) {
VCAP::CloudController::ProcessModel.make(
@@ -933,6 +969,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
)
}
@@ -941,6 +978,7 @@
instances: 5,
memory_in_mb: 10,
disk_in_mb: 20,
+ log_rate_limit_in_bytes_per_second: 40,
}
end
@@ -954,6 +992,7 @@
'instance-count' => 5,
'memory-in-mb' => 10,
'disk-in-mb' => 20,
+ 'log-rate-in-bytes-per-second' => 40,
'process-type' => 'web',
'app-id' => Digest::SHA256.hexdigest(process.app.guid),
'user-id' => Digest::SHA256.hexdigest(developer.guid),
@@ -1042,6 +1081,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
)
}
@@ -1055,6 +1095,7 @@
instances: 1,
memory: 100,
disk_quota: 200,
+ log_rate_limit: 400,
command: 'start worker',
)
}
@@ -1103,6 +1144,7 @@
'instances' => 2,
'memory_in_mb' => 1024,
'disk_in_mb' => 1024,
+ 'log_rate_limit_in_bytes_per_second' => 1_048_576,
'health_check' => {
'type' => 'port',
'data' => {
@@ -1136,6 +1178,7 @@
'instances' => 1,
'memory_in_mb' => 100,
'disk_in_mb' => 200,
+ 'log_rate_limit_in_bytes_per_second' => 400,
'health_check' => {
'type' => 'port',
'data' => {
@@ -1237,6 +1280,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
)
}
@@ -1253,6 +1297,7 @@
'instances' => 2,
'memory_in_mb' => 1024,
'disk_in_mb' => 1024,
+ 'log_rate_limit_in_bytes_per_second' => 1_048_576,
'health_check' => {
'type' => 'port',
'data' => {
@@ -1322,6 +1367,7 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
ports: [4444, 5555],
health_check_type: 'port',
@@ -1353,6 +1399,7 @@
'instances' => 2,
'memory_in_mb' => 1024,
'disk_in_mb' => 1024,
+ 'log_rate_limit_in_bytes_per_second' => 1_048_576,
'health_check' => {
'type' => 'http',
'data' => {
@@ -1435,7 +1482,9 @@
instances: 2,
memory: 1024,
disk_quota: 1024,
+ log_rate_limit: 1_048_576,
command: 'rackup',
+ state: 'STARTED',
)
end
@@ -1443,6 +1492,7 @@
instances: 5,
memory_in_mb: 10,
disk_in_mb: 20,
+ log_rate_limit_in_bytes_per_second: 40,
}
end
@@ -1459,6 +1509,7 @@
'instances' => 5,
'memory_in_mb' => 10,
'disk_in_mb' => 20,
+ 'log_rate_limit_in_bytes_per_second' => 40,
'health_check' => {
'type' => 'port',
'data' => {
@@ -1490,6 +1541,7 @@
expect(process.instances).to eq(5)
expect(process.memory).to eq(10)
expect(process.disk_quota).to eq(20)
+ expect(process.log_rate_limit).to eq(40)
events = VCAP::CloudController::Event.where(actor: developer.guid).all
@@ -1511,11 +1563,34 @@
'request' => {
'instances' => 5,
'memory_in_mb' => 10,
- 'disk_in_mb' => 20
+ 'disk_in_mb' => 20,
+ 'log_rate_limit_in_bytes_per_second' => 40,
}
})
end
+ context 'when the log rate limit would be exceeded by adding additional instances' do
+ let(:scale_request) do
+ {
+ instances: 5
+ }
+ end
+
+ before do
+ org.update(quota_definition: VCAP::CloudController::QuotaDefinition.make(log_rate_limit: 2_097_152))
+ end
+
+ it 'fails to scale the process' do
+ post "/v3/apps/#{app_model.guid}/processes/web/actions/scale", scale_request.to_json, developer_headers
+
+ expect(last_response.status).to eq(422)
+ expect(parsed_response['errors'][0]['detail']).to eq 'log_rate_limit exceeds organization log rate quota'
+
+ process.reload
+ expect(process.instances).to eq(2)
+ end
+ end
+
context 'when the user is assigned the space_supporter role' do
let(:space_supporter) do
user = VCAP::CloudController::User.make
@@ -1534,6 +1609,7 @@
expect(process.instances).to eq(5)
expect(process.memory).to eq(10)
expect(process.disk_quota).to eq(20)
+ expect(process.log_rate_limit).to eq(40)
end
end
@@ -1548,6 +1624,7 @@
'instance-count' => 5,
'memory-in-mb' => 10,
'disk-in-mb' => 20,
+ 'log-rate-in-bytes-per-second' => 40,
'process-type' => 'web',
'app-id' => Digest::SHA256.hexdigest(app_model.guid),
'user-id' => Digest::SHA256.hexdigest(developer.guid),
diff --git a/spec/request/space_manifests_spec.rb b/spec/request/space_manifests_spec.rb
index 3c2c4f6da9c..7fe1263b092 100644
--- a/spec/request/space_manifests_spec.rb
+++ b/spec/request/space_manifests_spec.rb
@@ -27,6 +27,7 @@
'instances' => 4,
'memory' => '2048MB',
'disk_quota' => '1.5GB',
+ 'log-rate-limit-per-second' => '1MB',
'buildpack' => buildpack.name,
'stack' => buildpack.stack,
'command' => 'new-command',
@@ -73,6 +74,7 @@
'instances' => 3,
'memory' => '2048MB',
'disk_quota' => '1.5GB',
+ 'log-rate-limit-per-second' => '100KB',
'buildpack' => buildpack.name,
'stack' => buildpack.stack,
'command' => 'newer-command',
@@ -172,6 +174,7 @@
expect(web_process.instances).to eq(4)
expect(web_process.memory).to eq(2048)
expect(web_process.disk_quota).to eq(1536)
+ expect(web_process.log_rate_limit).to eq(1_048_576)
expect(web_process.command).to eq('new-command')
expect(web_process.health_check_type).to eq('http')
expect(web_process.health_check_http_endpoint).to eq('/health')
@@ -244,6 +247,7 @@
'instances' => 4,
'memory' => '2048MB',
'disk_quota' => '1.5GB',
+ 'log-rate-limit-per-second' => '1GB',
'buildpack' => buildpack.name,
'stack' => buildpack.stack,
'command' => 'new-command',
@@ -267,6 +271,7 @@
'instances' => 4,
'memory' => '2048MB',
'disk_quota' => '1.5GB',
+ 'log-rate-limit-per-second' => '-1B',
'buildpack' => buildpack.name,
'stack' => buildpack.stack,
'command' => 'new-command',
@@ -304,6 +309,7 @@
expect(web_process.instances).to eq(4)
expect(web_process.memory).to eq(2048)
expect(web_process.disk_quota).to eq(1536)
+ expect(web_process.log_rate_limit).to eq(-1)
expect(web_process.command).to eq('new-command')
expect(web_process.health_check_type).to eq('http')
expect(web_process.health_check_http_endpoint).to eq('/health')
@@ -455,6 +461,56 @@
end
end
+ context 'when -1 is given as a log rate limit' do
+ let(:yml_manifest) do
+ {
+ 'version' => 1,
+ 'applications' => [
+ { 'name' => app1_model.name,
+ 'log-rate-limit-per-second' => -1
+ },
+ ]
+ }.to_yaml
+ end
+
+ it 'interprets the log rate limit as unlimited' do
+ post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest, yml_headers(user_header)
+
+ expect(last_response.status).to eq(202)
+ job_guid = VCAP::CloudController::PollableJobModel.last.guid
+ expect(last_response.headers['Location']).to match(%r(/v3/jobs/#{job_guid}))
+
+ Delayed::Worker.new.work_off
+ expect(VCAP::CloudController::PollableJobModel.find(guid: job_guid)).to be_complete,
+ VCAP::CloudController::PollableJobModel.find(guid: job_guid).cf_api_error
+
+ app1_model.reload
+ expect(app1_model.processes.first.log_rate_limit).to eq(-1)
+ end
+ end
+
+ context 'when applying the manifest to an app which is already exceeding the log rate limit' do
+ before do
+ app1_model.web_processes.first.update(state: VCAP::CloudController::ProcessModel::STARTED, instances: 4)
+ space.update(space_quota_definition:
+ VCAP::CloudController::SpaceQuotaDefinition.make(organization: space.organization, log_rate_limit: 0))
+ end
+
+ it 'successfully applies the manifest' do
+ post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest, yml_headers(user_header)
+
+ expect(last_response.status).to eq(202)
+
+ job_guid = VCAP::CloudController::PollableJobModel.last.guid
+ expect(last_response.headers['Location']).to match(%r(/v3/jobs/#{job_guid}))
+
+ Delayed::Worker.new.work_off
+ # job does not restart app, so applying the manifest succeeds
+ expect(VCAP::CloudController::PollableJobModel.find(guid: job_guid)).to be_complete,
+ VCAP::CloudController::PollableJobModel.find(guid: job_guid).cf_api_error
+ end
+ end
+
describe 'audit events' do
let!(:process1) { nil }
@@ -465,6 +521,7 @@
'instances' => 4,
'memory' => '2048MB',
'disk_quota' => '1.5GB',
+ 'log-rate-limit-per-second' => '300B',
'buildpack' => buildpack.name,
'stack' => buildpack.stack,
'command' => 'new-command',
diff --git a/spec/request/space_quotas_spec.rb b/spec/request/space_quotas_spec.rb
index 5c67463dc72..67a3dfed230 100644
--- a/spec/request/space_quotas_spec.rb
+++ b/spec/request/space_quotas_spec.rb
@@ -88,7 +88,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: nil,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
@@ -112,7 +113,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: nil,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
@@ -208,6 +210,7 @@ module VCAP::CloudController
expect(last_response).to have_status_code(200)
expect(space_quota_to_update.reload.app_task_limit).to eq(9)
expect(space_quota_to_update.reload.memory_limit).to eq(-1)
+ expect(space_quota_to_update.reload.log_rate_limit).to eq(-1)
expect(space_quota_to_update.reload.total_services).to eq(14)
expect(space_quota_to_update.reload.non_basic_services_allowed).to be_falsey
end
@@ -219,6 +222,7 @@ module VCAP::CloudController
expect(last_response).to have_status_code(200)
expect(space_quota_to_update.reload.app_task_limit).to eq(9)
expect(space_quota_to_update.reload.memory_limit).to eq(-1)
+ expect(space_quota_to_update.reload.log_rate_limit).to eq(-1)
expect(space_quota_to_update.reload.total_services).to eq(14)
expect(space_quota_to_update.reload.non_basic_services_allowed).to be_falsey
end
@@ -406,7 +410,8 @@ module VCAP::CloudController
total_memory_in_mb: nil,
per_process_memory_in_mb: nil,
total_instances: nil,
- per_app_tasks: nil
+ per_app_tasks: nil,
+ log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
@@ -495,7 +500,8 @@ module VCAP::CloudController
total_memory_in_mb: nil,
per_process_memory_in_mb: nil,
total_instances: nil,
- per_app_tasks: nil
+ per_app_tasks: nil,
+ log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
@@ -555,7 +561,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: 10,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: 3000
},
services: {
paid_services_allowed: false,
@@ -589,7 +596,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: 10,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: 3000
},
services: {
paid_services_allowed: false,
@@ -968,7 +976,8 @@ def make_space_quota_json(space_quota, associated_spaces=space_quota.spaces)
total_memory_in_mb: 20480,
per_process_memory_in_mb: nil,
total_instances: nil,
- per_app_tasks: 5
+ per_app_tasks: 5,
+ log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
diff --git a/spec/request/tasks_spec.rb b/spec/request/tasks_spec.rb
index 0b030affc90..c2946239d48 100644
--- a/spec/request/tasks_spec.rb
+++ b/spec/request/tasks_spec.rb
@@ -2,9 +2,14 @@
require 'request_spec_shared_examples'
RSpec.describe 'Tasks' do
+ let(:org_quota_definition) { VCAP::CloudController::QuotaDefinition.make(log_rate_limit: org_log_rate_limit) }
+ let(:space_quota_definition) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: org, log_rate_limit: space_log_rate_limit) }
+ let(:space_log_rate_limit) { -1 }
+ let(:org_log_rate_limit) { -1 }
+ let(:task_log_rate_limit_in_bytes_per_second) { -1 }
let(:user) { VCAP::CloudController::User.make }
- let(:org) { space.organization }
- let(:space) { VCAP::CloudController::Space.make }
+ let(:org) { VCAP::CloudController::Organization.make(quota_definition: org_quota_definition) }
+ let(:space) { VCAP::CloudController::Space.make(space_quota_definition: space_quota_definition, organization: org) }
let(:app_model) { VCAP::CloudController::AppModel.make(space_guid: space.guid) }
let(:droplet) do
VCAP::CloudController::DropletModel.make(
@@ -101,6 +106,7 @@
droplet: app_model.droplet,
memory_in_mb: 5,
disk_in_mb: 10,
+ log_rate_limit: 20,
)
task2 = VCAP::CloudController::TaskModel.make(
name: 'task two',
@@ -109,6 +115,7 @@
droplet: app_model.droplet,
memory_in_mb: 100,
disk_in_mb: 500,
+ log_rate_limit: 1024,
)
VCAP::CloudController::TaskModel.make(
app_guid: app_model.guid,
@@ -137,7 +144,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 5,
'disk_in_mb' => 10,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => 20,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task1.droplet.guid,
@@ -168,7 +176,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 100,
'disk_in_mb' => 500,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => 1024,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task2.droplet.guid,
@@ -216,6 +225,7 @@
droplet: app_model.droplet,
memory_in_mb: 5,
disk_in_mb: 10,
+ log_rate_limit: 20,
)
end
@@ -228,7 +238,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 256,
'disk_in_mb' => nil,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => -1,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task1.droplet.guid,
@@ -259,7 +270,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 5,
'disk_in_mb' => 10,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => 20,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task2.droplet.guid,
@@ -407,6 +419,7 @@
droplet: app_model.droplet,
memory_in_mb: 5,
disk_in_mb: 50,
+ log_rate_limit: 64,
)
end
let(:api_call) { lambda { |user_headers| get "/v3/tasks/#{task.guid}", nil, user_headers } }
@@ -418,7 +431,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 5,
'disk_in_mb' => 50,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => 64,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task.droplet.guid,
@@ -498,8 +512,10 @@
command: 'echo task',
app_guid: app_model.guid,
droplet_guid: app_model.droplet.guid,
- disk_in_mb: 50,
- memory_in_mb: 5)
+ disk_in_mb: 50,
+ memory_in_mb: 5,
+ log_rate_limit: 10,
+ )
}
let(:request_body) do
{
@@ -528,7 +544,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 5,
'disk_in_mb' => 50,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => 10,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task.droplet.guid,
@@ -605,6 +622,7 @@
state: 'CANCELING',
memory_in_mb: 256,
disk_in_mb: nil,
+ log_rate_limit_in_bytes_per_second: -1,
result: {
failure_reason: nil
},
@@ -683,6 +701,7 @@
droplet: app_model.droplet,
memory_in_mb: 5,
disk_in_mb: 50,
+ log_rate_limit: 64,
)
task2 = VCAP::CloudController::TaskModel.make(
name: 'task two',
@@ -691,6 +710,7 @@
droplet: app_model.droplet,
memory_in_mb: 100,
disk_in_mb: 500,
+ log_rate_limit: 256,
)
VCAP::CloudController::TaskModel.make(
app_guid: app_model.guid,
@@ -718,7 +738,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 5,
'disk_in_mb' => 50,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => 64,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task1.droplet.guid,
@@ -750,7 +771,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 100,
'disk_in_mb' => 500,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => 256,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task2.droplet.guid,
@@ -815,7 +837,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 256,
'disk_in_mb' => nil,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => -1,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task1.droplet.guid,
@@ -846,7 +869,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 5,
'disk_in_mb' => 10,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => -1,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => task2.droplet.guid,
@@ -997,6 +1021,7 @@
command: 'be rake && true',
memory_in_mb: 1234,
disk_in_mb: 1000,
+ log_rate_limit_in_bytes_per_second: task_log_rate_limit_in_bytes_per_second,
metadata: {
labels: {
bananas: 'gros_michel',
@@ -1029,7 +1054,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 1234,
'disk_in_mb' => 1000,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => task_log_rate_limit_in_bytes_per_second,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => droplet.guid,
@@ -1082,6 +1108,71 @@
})
end
+ describe 'log_rate_limit' do
+ context 'when the request does not specify a log rate limit' do
+ before do
+ TestConfig.config[:default_app_log_rate_limit_in_bytes_per_second] = 9876
+ end
+
+ it 'the default is applied' do
+ post "/v3/apps/#{app_model.guid}/tasks", body.except(:log_rate_limit_in_bytes_per_second).to_json, developer_headers
+ expect(last_response.status).to eq(202)
+ expect(VCAP::CloudController::TaskModel.last.log_rate_limit).to eq(9876)
+ end
+ end
+
+ context 'when there are org or space log rate limits' do
+ let(:space_log_rate_limit) { 200 }
+ let(:org_log_rate_limit) { 201 }
+
+ context 'when the task specifies a rate limit that fits in the quota' do
+ let(:task_log_rate_limit_in_bytes_per_second) { 199 }
+
+ it 'succeeds' do
+ post "/v3/apps/#{app_model.guid}/tasks", body.to_json, developer_headers
+ expect(last_response.status).to eq(202)
+ end
+ end
+
+ context 'when the task specifies unlimited rate limit' do
+ let(:task_log_rate_limit_in_bytes_per_second) { -1 }
+
+ it 'returns an error' do
+ post "/v3/apps/#{app_model.guid}/tasks", body.to_json, developer_headers
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message("log_rate_limit cannot be unlimited in organization '#{org.name}'.")
+ expect(last_response).to have_error_message("log_rate_limit cannot be unlimited in space '#{space.name}'.")
+ end
+ end
+
+ context 'when the task specifies a rate limit that does not fit in the quota' do
+ let(:task_log_rate_limit_in_bytes_per_second) { 202 }
+
+ context 'fails to fit in space quota' do
+ let(:space_log_rate_limit) { 200 }
+ let(:org_log_rate_limit) { -1 }
+
+ it 'returns an error' do
+ post "/v3/apps/#{app_model.guid}/tasks", body.to_json, developer_headers
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message('log_rate_limit exceeds space log rate quota')
+ end
+ end
+
+ context 'fails to fit in org quota' do
+ let(:space_log_rate_limit) { -1 }
+ let(:org_log_rate_limit) { 200 }
+
+ it 'returns an error' do
+ post "/v3/apps/#{app_model.guid}/tasks", body.to_json, developer_headers
+ expect(last_response.status).to eq(422)
+ expect(last_response).to have_error_message('log_rate_limit exceeds organization log rate quota')
+ end
+ end
+ end
+ end
+ end
+
context 'when requesting a specific droplet' do
let(:non_assigned_droplet) do
VCAP::CloudController::DropletModel.make(
@@ -1137,7 +1228,8 @@
'state' => 'RUNNING',
'memory_in_mb' => 1234,
'disk_in_mb' => 1000,
- 'result' => {
+ 'log_rate_limit_in_bytes_per_second' => process.log_rate_limit,
+ 'result' => {
'failure_reason' => nil
},
'droplet_guid' => droplet.guid,
diff --git a/spec/request/v2/apps_spec.rb b/spec/request/v2/apps_spec.rb
index 322616f35e6..ed0e34e18d0 100644
--- a/spec/request/v2/apps_spec.rb
+++ b/spec/request/v2/apps_spec.rb
@@ -61,6 +61,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STOPPED',
'version' => process.version,
'command' => 'hello_world',
@@ -228,6 +229,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STOPPED',
'version' => process.version,
'command' => 'hello_world',
@@ -447,6 +449,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STOPPED',
'version' => process.version,
'command' => 'app-command',
@@ -522,6 +525,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STOPPED',
'version' => process.version,
'command' => nil,
@@ -667,6 +671,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STOPPED',
'version' => process.version,
'command' => nil,
@@ -746,6 +751,7 @@
},
'memory' => 1024,
'instances' => 1,
+ 'log_rate_limit' => 1_048_576,
'disk_quota' => 1024,
'state' => 'STARTED',
'version' => process.version,
@@ -1012,6 +1018,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STARTED',
'version' => process.version,
'command' => nil,
@@ -1089,6 +1096,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STARTED',
'version' => process.version,
'command' => nil,
@@ -1217,6 +1225,7 @@
'environment_json' => nil,
'memory' => 1024,
'instances' => 1,
+ 'log_rate_limit' => 1_048_576,
'disk_quota' => 1024,
'state' => 'STOPPED',
'version' => process.version,
@@ -1497,6 +1506,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STARTED',
'version' => process.version,
'command' => nil,
@@ -1852,6 +1862,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STOPPED',
'version' => process.version,
'command' => nil,
diff --git a/spec/request/v2/routes_spec.rb b/spec/request/v2/routes_spec.rb
index c34d02d72e3..ae786b43285 100644
--- a/spec/request/v2/routes_spec.rb
+++ b/spec/request/v2/routes_spec.rb
@@ -147,6 +147,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1048576,
'state' => 'STOPPED',
'version' => process.version,
'command' => nil,
@@ -349,6 +350,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1048576,
'state' => 'STOPPED',
'version' => process.version,
'command' => nil,
diff --git a/spec/request/v2/service_bindings_spec.rb b/spec/request/v2/service_bindings_spec.rb
index d8705fff879..7485e5d6745 100644
--- a/spec/request/v2/service_bindings_spec.rb
+++ b/spec/request/v2/service_bindings_spec.rb
@@ -166,6 +166,7 @@
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1048576,
'state' => 'STOPPED',
'version' => process1.version,
'command' => nil,
diff --git a/spec/request/v2/spaces_spec.rb b/spec/request/v2/spaces_spec.rb
index e69b00436ec..713af2d6554 100644
--- a/spec/request/v2/spaces_spec.rb
+++ b/spec/request/v2/spaces_spec.rb
@@ -293,6 +293,7 @@
'health_check_timeout' => nil,
'health_check_type' => 'port',
'instances' => 1,
+ 'log_rate_limit' => 1_048_576,
'memory' => 1024,
'name' => process.name,
'package_state' => 'STAGED',
diff --git a/spec/traffic_controller/client_spec.rb b/spec/traffic_controller/client_spec.rb
deleted file mode 100644
index 7e9aaca1a54..00000000000
--- a/spec/traffic_controller/client_spec.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-require 'spec_helper'
-require 'traffic_controller/client'
-require 'openssl'
-
-module TrafficController
- RSpec.describe Client do
- let(:doppler_url) { 'https://doppler.example.com:4443' }
-
- subject(:client) { Client.new(url: doppler_url) }
- let(:expected_request_options) { { 'headers' => { 'Authorization' => 'bearer oauth-token' } } }
-
- def build_response_body(boundary, encoded_envelopes)
- body = []
- encoded_envelopes.each do |env|
- body << "--#{boundary}"
- body << ''
- body << env
- end
- body << "--#{boundary}--"
-
- body.join("\r\n")
- end
-
- describe '#container_metrics' do
- let(:auth_token) { 'bearer oauth-token' }
- let(:response_boundary) { SecureRandom.uuid }
- let(:response_body) do
- build_response_body(response_boundary, [
- Models::Envelope.new(origin: 'a', eventType: Models::Envelope::EventType::ContainerMetric).encode.to_s,
- Models::Envelope.new(origin: 'b', eventType: Models::Envelope::EventType::ContainerMetric).encode.to_s,
- ])
- end
- let(:response_status) { 200 }
- let(:response_headers) { { 'Content-Type' => "multipart/x-protobuf; boundary=#{response_boundary}" } }
-
- before do
- stub_request(:get, 'https://doppler.example.com:4443/apps/example-app-guid/containermetrics').
- with(expected_request_options).
- to_return(status: response_status, body: response_body, headers: response_headers)
- end
-
- it 'returns an array of Envelopes' do
- expect(client.container_metrics(auth_token: auth_token, source_guid: 'example-app-guid')).to match_array([
- Models::Envelope.new(origin: 'a', eventType: Models::Envelope::EventType::ContainerMetric),
- Models::Envelope.new(origin: 'b', eventType: Models::Envelope::EventType::ContainerMetric),
- ])
- expect(a_request(:get, 'https://doppler.example.com:4443/apps/example-app-guid/containermetrics')).to have_been_made
- end
-
- context 'when it does not return successfully' do
- let(:response_status) { 404 }
- let(:response_body) { 'not found' }
-
- it 'raises' do
- expect { client.container_metrics(auth_token: auth_token, source_guid: 'example-app-guid') }.to raise_error(ResponseError, /status: 404, body: not found/)
- end
- end
-
- context 'when it fails to make the request' do
- before do
- stub_request(:get, 'https://doppler.example.com:4443/apps/example-app-guid/containermetrics').to_raise(StandardError.new('error message'))
- end
-
- it 'raises' do
- expect { client.container_metrics(auth_token: auth_token, source_guid: 'example-app-guid') }.to raise_error(RequestError, /error message/)
- end
- end
-
- context 'when the response is not a valid multipart body' do
- let(:response_body) { '' }
-
- it 'returns an empty array' do
- expect(client.container_metrics(auth_token: auth_token, source_guid: 'example-app-guid')).to be_empty
- end
- end
-
- context 'when the response does not contain the boundary in the "Content-Type" header' do
- let(:response_headers) { { 'Content-Type' => 'potato' } }
-
- it 'raises' do
- expect {
- client.container_metrics(auth_token: auth_token, source_guid: 'example-app-guid')
- }.to raise_error(ResponseError, 'failed to find multipart boundary in Content-Type header')
- end
- end
-
- context 'when a part cannot be decoded by ProtoBuf' do
- let(:response_body) do
- build_response_body(response_boundary, [
- Models::Envelope.new(origin: 'a', eventType: Models::Envelope::EventType::ContainerMetric).encode.to_s,
- 'potato',
- ])
- end
-
- it 'raises' do
- expect { client.container_metrics(auth_token: auth_token, source_guid: 'example-app-guid') }.to raise_error(DecodeError)
- end
- end
- end
- end
-end
diff --git a/spec/unit/actions/build_create_spec.rb b/spec/unit/actions/build_create_spec.rb
index 5173244a3a2..cb4752ef97b 100644
--- a/spec/unit/actions/build_create_spec.rb
+++ b/spec/unit/actions/build_create_spec.rb
@@ -10,12 +10,14 @@ module VCAP::CloudController
user_audit_info: user_audit_info,
memory_limit_calculator: memory_limit_calculator,
disk_limit_calculator: disk_limit_calculator,
+ log_rate_limit_calculator: log_rate_limit_calculator,
environment_presenter: environment_builder
)
end
let(:memory_limit_calculator) { double(:memory_limit_calculator) }
let(:disk_limit_calculator) { double(:disk_limit_calculator) }
+ let(:log_rate_limit_calculator) { double(:log_rate_limit_calculator) }
let(:environment_builder) { double(:environment_builder) }
let(:user_audit_info) { UserAuditInfo.new(user_email: 'charles@las.gym', user_guid: '1234', user_name: 'charles') }
@@ -24,6 +26,7 @@ module VCAP::CloudController
{
staging_memory_in_mb: staging_memory_in_mb,
staging_disk_in_mb: staging_disk_in_mb,
+ staging_log_rate_limit_bytes_per_second: staging_log_rate_limit_bytes_per_second,
lifecycle: request_lifecycle,
}.deep_stringify_keys
end
@@ -50,7 +53,7 @@ module VCAP::CloudController
let(:space) { Space.make }
let(:org) { space.organization }
let(:app) { AppModel.make(space: space) }
- let!(:process) { ProcessModel.make(app: app, memory: 8192, disk_quota: 512) }
+ let!(:process) { ProcessModel.make(app: app, memory: 8192, disk_quota: 512, log_rate_limit: 7168) }
let(:buildpack_git_url) { 'http://example.com/repo.git' }
let(:stack) { Stack.default }
@@ -65,9 +68,11 @@ module VCAP::CloudController
let(:stager) { instance_double(Diego::Stager) }
let(:calculated_mem_limit) { 32 }
let(:calculated_staging_disk_in_mb) { 64 }
+ let(:calculated_staging_log_rate_limit) { 96 }
let(:staging_memory_in_mb) { 1024 }
let(:staging_disk_in_mb) { 2048 }
+ let(:staging_log_rate_limit_bytes_per_second) { 3072 }
let(:environment_variables) { 'random string' }
before do
@@ -76,6 +81,7 @@ module VCAP::CloudController
allow(stager).to receive(:stage)
allow(memory_limit_calculator).to receive(:get_limit).with(staging_memory_in_mb, space, org).and_return(calculated_mem_limit)
allow(disk_limit_calculator).to receive(:get_limit).with(staging_disk_in_mb).and_return(calculated_staging_disk_in_mb)
+ allow(log_rate_limit_calculator).to receive(:get_limit).with(staging_log_rate_limit_bytes_per_second, space, org).and_return(calculated_staging_log_rate_limit)
allow(environment_builder).to receive(:build).and_return(environment_variables)
end
@@ -93,6 +99,7 @@ module VCAP::CloudController
expect(build.package_guid).to eq(package.guid)
expect(build.staging_memory_in_mb).to eq(calculated_mem_limit)
expect(build.staging_disk_in_mb).to eq(calculated_staging_disk_in_mb)
+ expect(build.staging_log_rate_limit).to eq(calculated_staging_log_rate_limit)
expect(build.lifecycle_data.to_hash).to eq(lifecycle_data)
expect(build.created_by_user_guid).to eq('1234')
expect(build.created_by_user_name).to eq('charles')
@@ -174,6 +181,7 @@ module VCAP::CloudController
expect(staging_details.staging_guid).to eq(build.guid)
expect(staging_details.staging_memory_in_mb).to eq(calculated_mem_limit)
expect(staging_details.staging_disk_in_mb).to eq(calculated_staging_disk_in_mb)
+ expect(staging_details.staging_log_rate_limit_bytes_per_second).to eq(calculated_staging_log_rate_limit)
expect(staging_details.environment_variables).to eq(environment_variables)
expect(staging_details.lifecycle).to eq(lifecycle)
expect(staging_details.isolation_segment).to be_nil
@@ -210,6 +218,20 @@ module VCAP::CloudController
end
end
+ context 'when staging log rate limit is not specified in the message' do
+ before do
+ allow(log_rate_limit_calculator).to receive(:get_limit).with(process.log_rate_limit, space, org).and_return(process.log_rate_limit)
+ end
+ let(:staging_log_rate_limit_bytes_per_second) { nil }
+
+ it 'uses the app web process log rate limit for staging log rate limit' do
+ expect(log_rate_limit_calculator).to receive(:get_limit).with(process.log_rate_limit, space, org)
+
+ build = action.create_and_stage(package: package, lifecycle: lifecycle)
+ expect(build.staging_log_rate_limit).to eq(process.log_rate_limit)
+ end
+ end
+
describe 'isolation segments' do
let(:assigner) { VCAP::CloudController::IsolationSegmentAssign.new }
let(:isolation_segment_model) { VCAP::CloudController::IsolationSegmentModel.make }
@@ -343,6 +365,28 @@ module VCAP::CloudController
end
end
+ context 'when the org quota is exceeded' do
+ before do
+ allow(log_rate_limit_calculator).to receive(:get_limit).and_raise(QuotaValidatingStagingLogRateLimitCalculator::OrgQuotaExceeded, 'some-message')
+ end
+ it 'raises a LogRateLimitOrgQuotaExceeded error' do
+ expect {
+ action.create_and_stage(package: package, lifecycle: lifecycle, metadata: metadata)
+ }.to raise_error(::VCAP::CloudController::BuildCreate::LogRateLimitOrgQuotaExceeded, 'some-message')
+ end
+ end
+
+ context 'when the space quota is exceeded' do
+ before do
+ allow(log_rate_limit_calculator).to receive(:get_limit).and_raise(QuotaValidatingStagingLogRateLimitCalculator::SpaceQuotaExceeded, 'some-message')
+ end
+ it 'raises a LogRateLimitSpaceQuotaExceeded error' do
+ expect {
+ action.create_and_stage(package: package, lifecycle: lifecycle, metadata: metadata)
+ }.to raise_error(::VCAP::CloudController::BuildCreate::LogRateLimitSpaceQuotaExceeded, 'some-message')
+ end
+ end
+
describe 'using custom buildpacks' do
let!(:app) { AppModel.make(space: space) }
diff --git a/spec/unit/actions/deployment_create_spec.rb b/spec/unit/actions/deployment_create_spec.rb
index e683742beed..fe9e414f1fd 100644
--- a/spec/unit/actions/deployment_create_spec.rb
+++ b/spec/unit/actions/deployment_create_spec.rb
@@ -5,7 +5,7 @@
module VCAP::CloudController
RSpec.describe DeploymentCreate do
let(:app) { AppModel.make(desired_state: ProcessModel::STARTED) }
- let!(:web_process) { ProcessModel.make(app: app, instances: 3) }
+ let!(:web_process) { ProcessModel.make(app: app, instances: 3, log_rate_limit: 101) }
let(:original_droplet) { DropletModel.make(app: app, process_types: { 'web' => 'asdf' }) }
let(:next_droplet) { DropletModel.make(app: app, process_types: { 'web' => '1234' }) }
let!(:route1) { Route.make(space: app.space) }
@@ -169,6 +169,7 @@ module VCAP::CloudController
expect(deploying_web_process.memory).to eq(web_process.memory)
expect(deploying_web_process.file_descriptors).to eq(web_process.file_descriptors)
expect(deploying_web_process.disk_quota).to eq(web_process.disk_quota)
+ expect(deploying_web_process.log_rate_limit).to eq(web_process.log_rate_limit)
expect(deploying_web_process.metadata).to eq(web_process.metadata)
expect(deploying_web_process.detected_buildpack).to eq(web_process.detected_buildpack)
expect(deploying_web_process.health_check_timeout).to eq(web_process.health_check_timeout)
@@ -206,6 +207,7 @@ module VCAP::CloudController
health_check_type: 'http',
health_check_http_endpoint: '/old_dawg',
health_check_invocation_timeout: 9,
+ log_rate_limit: 11,
enable_ssh: true,
ports: [],
)
@@ -227,6 +229,7 @@ module VCAP::CloudController
health_check_type: 'port',
health_check_http_endpoint: '/new_cat',
health_check_invocation_timeout: 10,
+ log_rate_limit: 12,
enable_ssh: false,
ports: nil,
)
@@ -244,6 +247,7 @@ module VCAP::CloudController
expect(deploying_web_process.memory).to eq(newer_web_process.memory)
expect(deploying_web_process.file_descriptors).to eq(newer_web_process.file_descriptors)
expect(deploying_web_process.disk_quota).to eq(newer_web_process.disk_quota)
+ expect(deploying_web_process.log_rate_limit).to eq(newer_web_process.log_rate_limit)
expect(deploying_web_process.metadata).to eq(newer_web_process.metadata)
expect(deploying_web_process.detected_buildpack).to eq(newer_web_process.detected_buildpack)
expect(deploying_web_process.health_check_timeout).to eq(newer_web_process.health_check_timeout)
diff --git a/spec/unit/actions/organization_quotas_create_spec.rb b/spec/unit/actions/organization_quotas_create_spec.rb
index 04349c77d44..3efe2d75acc 100644
--- a/spec/unit/actions/organization_quotas_create_spec.rb
+++ b/spec/unit/actions/organization_quotas_create_spec.rb
@@ -17,7 +17,8 @@ module VCAP::CloudController
total_memory_in_mb: 1,
per_process_memory_in_mb: 2,
total_instances: 3,
- per_app_tasks: 4
+ per_app_tasks: 4,
+ log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
@@ -57,6 +58,7 @@ module VCAP::CloudController
expect(organization_quota.instance_memory_limit).to eq(2)
expect(organization_quota.app_instance_limit).to eq(3)
expect(organization_quota.app_task_limit).to eq(4)
+ expect(organization_quota.log_rate_limit).to eq(2000)
expect(organization_quota.total_services).to eq(5)
expect(organization_quota.total_service_keys).to eq(6)
@@ -79,6 +81,7 @@ module VCAP::CloudController
expect(organization_quota.instance_memory_limit).to eq(-1)
expect(organization_quota.app_instance_limit).to eq(-1)
expect(organization_quota.app_task_limit).to eq(-1)
+ expect(organization_quota.log_rate_limit).to eq(-1)
expect(organization_quota.total_services).to eq(-1)
expect(organization_quota.total_service_keys).to eq(-1)
diff --git a/spec/unit/actions/organization_quotas_update_spec.rb b/spec/unit/actions/organization_quotas_update_spec.rb
index f4a9bdefc6e..e75f9151bae 100644
--- a/spec/unit/actions/organization_quotas_update_spec.rb
+++ b/spec/unit/actions/organization_quotas_update_spec.rb
@@ -15,7 +15,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: 8,
- per_app_tasks: nil
+ per_app_tasks: nil,
+ log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
@@ -49,6 +50,7 @@ module VCAP::CloudController
expect(updated_organization_quota.instance_memory_limit).to eq(1024)
expect(updated_organization_quota.app_instance_limit).to eq(8)
expect(updated_organization_quota.app_task_limit).to eq(-1)
+ expect(updated_organization_quota.log_rate_limit).to eq(2000)
expect(updated_organization_quota.total_services).to eq(10)
expect(updated_organization_quota.total_service_keys).to eq(20)
@@ -64,6 +66,7 @@ module VCAP::CloudController
updated_organization_quota = OrganizationQuotasUpdate.update(org_quota, minimum_message)
expect(updated_organization_quota.name).to eq('org_quota_name')
+ expect(updated_organization_quota.log_rate_limit).to eq(-1)
end
context 'when a model validation fails' do
diff --git a/spec/unit/actions/process_scale_spec.rb b/spec/unit/actions/process_scale_spec.rb
index 45688d65dd0..7d603313059 100644
--- a/spec/unit/actions/process_scale_spec.rb
+++ b/spec/unit/actions/process_scale_spec.rb
@@ -4,7 +4,14 @@
module VCAP::CloudController
RSpec.describe ProcessScale do
subject(:process_scale) { ProcessScale.new(user_audit_info, process, message) }
- let(:valid_message_params) { { instances: 2, memory_in_mb: 100, disk_in_mb: 200 } }
+ let(:valid_message_params) do
+ {
+ instances: 2,
+ memory_in_mb: 100,
+ disk_in_mb: 200,
+ log_rate_limit_in_bytes_per_second: 409_600
+ }
+ end
let(:message) { ProcessScaleMessage.new(valid_message_params) }
let(:app) { AppModel.make }
let!(:process) { ProcessModelFactory.make(disk_quota: 50, app: app) }
@@ -15,12 +22,14 @@ module VCAP::CloudController
expect(process.instances).not_to eq(2)
expect(process.memory).not_to eq(100)
expect(process.disk_quota).not_to eq(200)
+ expect(process.log_rate_limit).not_to eq(409_600)
process_scale.scale
expect(process.reload.instances).to eq(2)
expect(process.reload.memory).to eq(100)
expect(process.reload.disk_quota).to eq(200)
+ expect(process.log_rate_limit).to eq(409_600)
end
it 'does not set instances if the user did not request it' do
@@ -50,6 +59,24 @@ module VCAP::CloudController
expect(process.disk_quota).to eq(original_value)
end
+ it 'does not set log quota if the user did not request it' do
+ valid_message_params.delete(:log_rate_limit_in_bytes_per_second)
+ original_value = process.log_rate_limit
+
+ process_scale.scale
+
+ expect(process.log_rate_limit).to eq(original_value)
+ end
+
+ it 'does not set log quota if null is provided' do
+ valid_message_params[:log_rate_limit_in_bytes_per_second] = nil
+ original_value = process.log_rate_limit
+
+ process_scale.scale
+
+ expect(process.log_rate_limit).to eq(original_value)
+ end
+
describe 'audit events' do
it 'creates a process audit event' do
expect(Repositories::ProcessEventRepository).to receive(:record_scale).with(
@@ -58,7 +85,8 @@ module VCAP::CloudController
{
'instances' => 2,
'memory_in_mb' => 100,
- 'disk_in_mb' => 200
+ 'disk_in_mb' => 200,
+ 'log_rate_limit_in_bytes_per_second' => 409_600,
},
manifest_triggered: false
)
@@ -76,7 +104,8 @@ module VCAP::CloudController
{
'instances' => 2,
'memory_in_mb' => 100,
- 'disk_in_mb' => 200
+ 'disk_in_mb' => 200,
+ 'log_rate_limit_in_bytes_per_second' => 409_600,
},
manifest_triggered: true
)
@@ -109,12 +138,14 @@ module VCAP::CloudController
expect(process.instances).to eq(1)
expect(process.memory).to eq(1024)
expect(process.disk_quota).to eq(50)
+ expect(process.log_rate_limit).to eq(1_048_576)
process_scale.scale
expect(process.reload.instances).to eq(2)
expect(process.reload.memory).to eq(100)
expect(process.reload.disk_quota).to eq(200)
+ expect(process.reload.log_rate_limit).to eq(409_600)
end
it 'fails if the process is web' do
@@ -123,12 +154,14 @@ module VCAP::CloudController
expect(process.instances).to eq(1)
expect(process.memory).to eq(1024)
expect(process.disk_quota).to eq(50)
+ expect(process.log_rate_limit).to eq(1_048_576)
expect { process_scale.scale }.to raise_error(ProcessScale::InvalidProcess, 'Cannot scale this process while a deployment is in flight.')
expect(process.reload.instances).to eq(1)
expect(process.reload.memory).to eq(1024)
expect(process.reload.disk_quota).to eq(50)
+ expect(process.reload.log_rate_limit).to eq(1_048_576)
end
end
end
diff --git a/spec/unit/actions/space_diff_manifest_spec.rb b/spec/unit/actions/space_diff_manifest_spec.rb
index 341e36004c9..85340f15173 100644
--- a/spec/unit/actions/space_diff_manifest_spec.rb
+++ b/spec/unit/actions/space_diff_manifest_spec.rb
@@ -21,6 +21,7 @@ module VCAP::CloudController
'instances' => process1.instances,
'memory' => '1024M',
'disk_quota' => '1024M',
+ 'log-rate-limit-per-second' => '1M',
'health-check-type' => process1.health_check_type
},
{
@@ -28,6 +29,7 @@ module VCAP::CloudController
'instances' => process2.instances,
'memory' => '1024M',
'disk_quota' => '1024M',
+ 'log-rate-limit-per-second' => '1M',
'health-check-type' => process2.health_check_type
}
]
@@ -69,13 +71,13 @@ module VCAP::CloudController
context 'when there are changes in the manifest' do
before do
- default_manifest['applications'][0]['random_route'] = true
+ default_manifest['applications'][0]['random-route'] = true
default_manifest['applications'][0]['stack'] = 'big brother'
end
it 'returns the correct diff' do
expect(subject).to match_array([
- { 'op' => 'add', 'path' => '/applications/0/random_route', 'value' => true },
+ { 'op' => 'add', 'path' => '/applications/0/random-route', 'value' => true },
{ 'op' => 'replace', 'path' => '/applications/0/stack', 'was' => process1.stack.name, 'value' => 'big brother' },
])
end
@@ -264,6 +266,7 @@ module VCAP::CloudController
before do
default_manifest['applications'][0]['processes'][0]['memory'] = '1G'
default_manifest['applications'][0]['processes'][0]['disk_quota'] = '1G'
+ default_manifest['applications'][0]['processes'][0]['log-rate-limit-per-second'] = '1024K'
end
it 'returns an empty diff' do
expect(subject).to eq([])
@@ -274,11 +277,15 @@ module VCAP::CloudController
before do
default_manifest['applications'][0]['processes'][0]['memory'] = '2G'
default_manifest['applications'][0]['processes'][0]['disk_quota'] = '4G'
+ default_manifest['applications'][0]['processes'][0]['health-check-type'] = 'process'
+ default_manifest['applications'][0]['processes'][0]['log-rate-limit-per-second'] = '2G'
end
- it 'returns the diff formatted as megabytes' do
+ it 'returns the diff formatted' do
expect(subject).to eq([
{ 'op' => 'replace', 'path' => '/applications/0/processes/0/memory', 'value' => '2048M', 'was' => '1024M' },
{ 'op' => 'replace', 'path' => '/applications/0/processes/0/disk_quota', 'value' => '4096M', 'was' => '1024M' },
+ { 'op' => 'replace', 'path' => '/applications/0/processes/0/log-rate-limit-per-second', 'value' => '2G', 'was' => '1M' },
+ { 'op' => 'replace', 'path' => '/applications/0/processes/0/health-check-type', 'value' => 'process', 'was' => 'port' },
])
end
end
@@ -317,14 +324,111 @@ module VCAP::CloudController
end
context 'when updating app-level configurations' do
- before do
- default_manifest['applications'][0]['memory'] = '1G'
- default_manifest['applications'][0]['disk_quota'] = '1G'
+ context 'when nothing has changed' do
+ before do
+ default_manifest['applications'][0]['log-rate-limit-per-second'] = '1024K'
+ end
+
+ it 'returns an empty diff if the field is equivalent' do
+ expect(subject).to eq([])
+ end
end
- it 'returns an empty diff if the field is equivalent' do
- expect(subject).to eq([])
+ context 'when trying to change memory and disk quota' do
+ before do
+ default_manifest['applications'][0]['memory'] = '99G'
+ default_manifest['applications'][0]['disk_quota'] = '99G'
+ end
+
+ it 'returns an empty diff because for some reason we ignore these fields at the app level' do
+ expect(subject).to eq([])
+ end
end
+
+ context 'when things have changed' do
+ before do
+ default_manifest['applications'][0]['health-check-type'] = 'process'
+ default_manifest['applications'][0]['instances'] = 9
+ default_manifest['applications'][0]['log-rate-limit-per-second'] = '1G'
+ end
+
+ it 'displays in the diff' do
+ expect(subject).to eq([
+ {
+ 'op' => 'replace',
+ 'path' => '/applications/0/health-check-type',
+ 'was' => 'port',
+ 'value' => 'process'
+ },
+ {
+ 'op' => 'replace',
+ 'path' => '/applications/0/instances',
+ 'was' => 1,
+ 'value' => 9
+ },
+ {
+ 'op' => 'replace',
+ 'path' => '/applications/0/log-rate-limit-per-second',
+ 'was' => '1M',
+ 'value' => '1G'
+ }
+ ])
+ end
+ end
+ end
+ end
+
+ describe 'log-rate-limit-per-second' do
+ it 'can handle -1 as a string for unlimited' do
+ default_manifest['applications'][0]['log-rate-limit-per-second'] = '-1'
+
+ expect(subject).to include(
+ {
+ 'op' => 'replace',
+ 'path' => '/applications/0/log-rate-limit-per-second',
+ 'value' => '-1',
+ 'was' => '1M'
+ }
+ )
+ end
+
+ it 'can handle -1 as a number for unlimited' do
+ default_manifest['applications'][0]['log-rate-limit-per-second'] = -1
+
+ expect(subject).to include(
+ {
+ 'op' => 'replace',
+ 'path' => '/applications/0/log-rate-limit-per-second',
+ 'value' => '-1',
+ 'was' => '1M'
+ }
+ )
+ end
+
+ it 'can handle 0 as a string without units' do
+ default_manifest['applications'][0]['log-rate-limit-per-second'] = '0'
+
+ expect(subject).to include(
+ {
+ 'op' => 'replace',
+ 'path' => '/applications/0/log-rate-limit-per-second',
+ 'value' => '0',
+ 'was' => '1M'
+ }
+ )
+ end
+
+ it 'can handle 0 as a number without units' do
+ default_manifest['applications'][0]['log-rate-limit-per-second'] = 0
+
+ expect(subject).to include(
+ {
+ 'op' => 'replace',
+ 'path' => '/applications/0/log-rate-limit-per-second',
+ 'value' => '0',
+ 'was' => '1M'
+ }
+ )
end
end
diff --git a/spec/unit/actions/space_quota_update_spec.rb b/spec/unit/actions/space_quota_update_spec.rb
index 86b7feae606..e98a366a899 100644
--- a/spec/unit/actions/space_quota_update_spec.rb
+++ b/spec/unit/actions/space_quota_update_spec.rb
@@ -23,7 +23,8 @@ module VCAP::CloudController
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: 8,
- per_app_tasks: nil
+ per_app_tasks: nil,
+ log_rate_limit_in_bytes_per_second: 2000,
},
services: {
paid_services_allowed: false,
@@ -50,6 +51,7 @@ module VCAP::CloudController
expect(updated_space_quota.instance_memory_limit).to eq(1024)
expect(updated_space_quota.app_instance_limit).to eq(8)
expect(updated_space_quota.app_task_limit).to eq(-1)
+ expect(updated_space_quota.log_rate_limit).to eq(2000)
expect(updated_space_quota.total_services).to eq(10)
expect(updated_space_quota.total_service_keys).to eq(20)
@@ -63,6 +65,7 @@ module VCAP::CloudController
updated_space_quota = SpaceQuotaUpdate.update(space_quota, minimum_message)
expect(updated_space_quota.name).to eq('space_quota_name')
+ expect(updated_space_quota.log_rate_limit).to eq(-1)
end
context 'when a model validation fails' do
diff --git a/spec/unit/actions/space_quotas_create_spec.rb b/spec/unit/actions/space_quotas_create_spec.rb
index aac7774bd0a..a40d9a45032 100644
--- a/spec/unit/actions/space_quotas_create_spec.rb
+++ b/spec/unit/actions/space_quotas_create_spec.rb
@@ -30,6 +30,7 @@ module VCAP::CloudController
per_process_memory_in_mb: 6,
total_instances: 7,
per_app_tasks: 8,
+ log_rate_limit_in_bytes_per_second: 2000,
},
services: {
paid_services_allowed: false,
@@ -68,6 +69,7 @@ module VCAP::CloudController
expect(space_quota.instance_memory_limit).to eq(-1)
expect(space_quota.app_instance_limit).to eq(-1)
expect(space_quota.app_task_limit).to eq(-1)
+ expect(space_quota.log_rate_limit).to eq(-1)
expect(space_quota.total_services).to eq(-1)
expect(space_quota.total_service_keys).to eq(-1)
@@ -90,6 +92,7 @@ module VCAP::CloudController
expect(space_quota.instance_memory_limit).to eq(6)
expect(space_quota.app_instance_limit).to eq(7)
expect(space_quota.app_task_limit).to eq(8)
+ expect(space_quota.log_rate_limit).to eq(2000)
expect(space_quota.total_services).to eq(9)
expect(space_quota.total_service_keys).to eq(10)
diff --git a/spec/unit/actions/task_create_spec.rb b/spec/unit/actions/task_create_spec.rb
index 09119ba6245..d029f136acd 100644
--- a/spec/unit/actions/task_create_spec.rb
+++ b/spec/unit/actions/task_create_spec.rb
@@ -8,7 +8,8 @@ module VCAP::CloudController
Config.new({
maximum_app_disk_in_mb: 4096,
default_app_memory: 1024,
- default_app_disk_in_mb: 1024
+ default_app_disk_in_mb: 1024,
+ default_app_log_rate_limit_in_bytes_per_second: 999
})
end
@@ -193,6 +194,28 @@ module VCAP::CloudController
end
end
+ describe 'log_rate_limit' do
+ context 'when log_rate_limit is specified' do
+ let(:message) { TaskCreateMessage.new name: name, command: command, log_rate_limit_in_bytes_per_second: 100 }
+
+ it 'returns what is in the message' do
+ task = task_create_action.create(app, message, user_audit_info)
+
+ expect(task.log_rate_limit).to eq(100)
+ end
+ end
+
+ context 'when log_rate_limit is not specified' do
+ let(:message) { TaskCreateMessage.new name: name, command: command }
+
+ it 'returns the default' do
+ task = task_create_action.create(app, message, user_audit_info)
+
+ expect(task.log_rate_limit).to eq(999)
+ end
+ end
+ end
+
describe 'disk_in_mb' do
before { config.set(:default_app_disk_in_mb, 200) }
@@ -327,6 +350,32 @@ module VCAP::CloudController
end
end
+ describe 'log_rate_limit' do
+ before do
+ config.set(:default_app_log_rate_limit_in_bytes_per_second, 999)
+ end
+
+ context 'when there is a template and the message does not specify log_rate_limit_in_bytes_per_second' do
+ let(:process) { VCAP::CloudController::ProcessModel.make(app: app, type: 'web', log_rate_limit: 23) }
+ let(:message) { TaskCreateMessage.new(name: name, command: 'ok', disk_in_mb: 2048, template: { process: { guid: process.guid } }) }
+
+ it 'uses the memory from the template process' do
+ task = task_create_action.create(app, message, user_audit_info)
+ expect(task.log_rate_limit).to eq(23)
+ end
+ end
+
+ context 'when there is a template and the message specifies log_rate_limit_in_bytes_per_second' do
+ let(:process) { VCAP::CloudController::ProcessModel.make(app: app, type: 'web', log_rate_limit: 23) }
+ let(:message) { TaskCreateMessage.new(name: name, command: 'ok', log_rate_limit_in_bytes_per_second: 2048, template: { process: { guid: process.guid } }) }
+
+ it 'uses the memory from the message' do
+ task = task_create_action.create(app, message, user_audit_info)
+ expect(task.log_rate_limit).to eq(2048)
+ end
+ end
+ end
+
describe 'disk_in_mb' do
before do
config.set(:default_app_disk_in_mb, 4096)
diff --git a/spec/unit/actions/v2/app_stage_spec.rb b/spec/unit/actions/v2/app_stage_spec.rb
index cf8ecfc6243..9ee71ece3e2 100644
--- a/spec/unit/actions/v2/app_stage_spec.rb
+++ b/spec/unit/actions/v2/app_stage_spec.rb
@@ -100,10 +100,10 @@ module V2
end
end
- context 'when SpaceQuotaExceeded error is raised' do
+ context 'when MemorySpaceQuotaExceeded error is raised' do
before do
allow(build_create).to receive(:create_and_stage_without_event).and_raise(
- BuildCreate::SpaceQuotaExceeded.new('helpful message')
+ BuildCreate::MemorySpaceQuotaExceeded.new('helpful message')
)
end
@@ -115,10 +115,10 @@ module V2
end
end
- context 'when OrgQuotaExceeded error is raised' do
+ context 'when MemoryOrgQuotaExceeded error is raised' do
before do
allow(build_create).to receive(:create_and_stage_without_event).and_raise(
- BuildCreate::OrgQuotaExceeded.new('helpful message')
+ BuildCreate::MemoryOrgQuotaExceeded.new('helpful message')
)
end
@@ -141,6 +141,32 @@ module V2
end
end
end
+
+ context 'when LogRateLimitSpaceQuotaExceeded error is raised' do
+ before do
+ allow(build_create).to receive(:create_and_stage_without_event).and_raise(
+ BuildCreate::LogRateLimitSpaceQuotaExceeded.new('helpful message')
+ )
+ end
+
+ it 'translates it to an ApiError' do
+ expect { action.stage(process) }.to(raise_error(CloudController::Errors::ApiError,
+ /helpful message/)) { |err| expect(err.details.name).to eq('SpaceQuotaLogRateLimitExceeded') }
+ end
+ end
+
+ context 'when LogRateLimitOrgQuotaExceeded error is raised' do
+ before do
+ allow(build_create).to receive(:create_and_stage_without_event).and_raise(
+ BuildCreate::LogRateLimitOrgQuotaExceeded.new('helpful message')
+ )
+ end
+
+ it 'translates it to an ApiError' do
+ expect { action.stage(process) }.to(raise_error(CloudController::Errors::ApiError,
+ /helpful message/)) { |err| expect(err.details.name).to eq('OrgQuotaLogRateLimitExceeded') }
+ end
+ end
end
context 'telemetry' do
diff --git a/spec/unit/controllers/runtime/apps_controller_spec.rb b/spec/unit/controllers/runtime/apps_controller_spec.rb
index 30ef40adf9f..7e86b70c17d 100644
--- a/spec/unit/controllers/runtime/apps_controller_spec.rb
+++ b/spec/unit/controllers/runtime/apps_controller_spec.rb
@@ -61,6 +61,7 @@ module VCAP::CloudController
console: { type: 'bool', default: false },
debug: { type: 'string' },
disk_quota: { type: 'integer' },
+ log_rate_limit: { type: 'integer' },
environment_json: { type: 'hash', default: {} },
health_check_http_endpoint: { type: 'string' },
health_check_timeout: { type: 'integer' },
@@ -88,6 +89,7 @@ module VCAP::CloudController
console: { type: 'bool' },
debug: { type: 'string' },
disk_quota: { type: 'integer' },
+ log_rate_limit: { type: 'integer' },
environment_json: { type: 'hash' },
health_check_http_endpoint: { type: 'string' },
health_check_timeout: { type: 'integer' },
diff --git a/spec/unit/controllers/v3/builds_controller_spec.rb b/spec/unit/controllers/v3/builds_controller_spec.rb
index 263dc929717..e1900af52fb 100644
--- a/spec/unit/controllers/v3/builds_controller_spec.rb
+++ b/spec/unit/controllers/v3/builds_controller_spec.rb
@@ -521,10 +521,10 @@
end
end
- context 'when the space quota is exceeded' do
+ context 'when the space memory quota is exceeded' do
before do
allow(build_create).to receive(:create_and_stage).and_raise(
- VCAP::CloudController::BuildCreate::SpaceQuotaExceeded.new('helpful message')
+ VCAP::CloudController::BuildCreate::MemorySpaceQuotaExceeded.new('helpful message')
)
end
@@ -537,10 +537,10 @@
end
end
- context 'when the org quota is exceeded' do
+ context 'when the org memory quota is exceeded' do
before do
allow(build_create).to receive(:create_and_stage).and_raise(
- VCAP::CloudController::BuildCreate::OrgQuotaExceeded.new('helpful message')
+ VCAP::CloudController::BuildCreate::MemoryOrgQuotaExceeded.new('helpful message')
)
end
@@ -565,6 +565,38 @@
expect(response.body).to include('disk limit exceeded')
end
end
+
+ context 'when the space log rate limit quota is exceeded' do
+ before do
+ allow(build_create).to receive(:create_and_stage).and_raise(
+ VCAP::CloudController::BuildCreate::LogRateLimitSpaceQuotaExceeded.new('helpful message')
+ )
+ end
+
+ it 'returns 422 Unprocessable' do
+ post :create, params: req_body, as: :json
+
+ expect(response.status).to eq(422)
+ expect(response.body).to include("space's log rate limit exceeded")
+ expect(response.body).to include('helpful message')
+ end
+ end
+
+ context 'when the org log rate limit quota is exceeded' do
+ before do
+ allow(build_create).to receive(:create_and_stage).and_raise(
+ VCAP::CloudController::BuildCreate::LogRateLimitOrgQuotaExceeded.new('helpful message')
+ )
+ end
+
+ it 'returns 422 Unprocessable' do
+ post :create, params: req_body, as: :json
+
+ expect(response.status).to eq(422)
+ expect(response.body).to include("organization's log rate limit exceeded")
+ expect(response.body).to include('helpful message')
+ end
+ end
end
describe 'metadata' do
diff --git a/spec/unit/lib/cloud_controller/app_manifest/byte_converter_spec.rb b/spec/unit/lib/cloud_controller/app_manifest/byte_converter_spec.rb
index 8c9d8274f1a..bbc0fff0073 100644
--- a/spec/unit/lib/cloud_controller/app_manifest/byte_converter_spec.rb
+++ b/spec/unit/lib/cloud_controller/app_manifest/byte_converter_spec.rb
@@ -126,5 +126,77 @@ module VCAP::CloudController
end
end
end
+
+ describe '#convert_to_b' do
+ context 'when given 1M' do
+ let(:byte_value) { '1M' }
+
+ it 'returns the value in bytes' do
+ expect(subject.convert_to_b(byte_value)).to eq(1_048_576)
+ end
+ end
+ end
+
+ describe '#human_readable_byte_value' do
+ context 'when given nil' do
+ let(:byte_value) { nil }
+
+ it 'returns nil' do
+ expect(subject.human_readable_byte_value(byte_value)).to be_nil
+ end
+ end
+
+ context 'when given 1M in bytes' do
+ let(:byte_value) { 1_048_576 }
+
+ it 'returns the human readable value' do
+ expect(subject.human_readable_byte_value(byte_value)).to eq('1M')
+ end
+ end
+
+ context 'when given 1G in bytes' do
+ let(:byte_value) { 1_073_741_824 }
+
+ it 'returns the human readable value' do
+ expect(subject.human_readable_byte_value(byte_value)).to eq('1G')
+ end
+ end
+
+ context 'when given 1.1M in bytes' do
+ let(:byte_value) { 1_153_434 }
+
+ it 'returns the human readable value in bytes to avoid losing precision' do
+ expect(subject.human_readable_byte_value(byte_value)).to eq('1153434B')
+ end
+ end
+
+ context 'when given 1M + 1K' do
+ let(:byte_value) { 1049600 }
+
+ it 'returns the human readable value in kilobytes to avoid losing precision' do
+ expect(subject.human_readable_byte_value(byte_value)).to eq('1025K')
+ end
+ end
+
+ context 'when given a string' do
+ let(:byte_value) { 'not-a-number' }
+
+ it 'raises an error' do
+ expect {
+ subject.human_readable_byte_value(byte_value)
+ }.to raise_error(ByteConverter::InvalidBytesError)
+ end
+ end
+
+ context 'when given a float' do
+ let(:byte_value) { 1.1 }
+
+ it 'raises an error' do
+ expect {
+ subject.human_readable_byte_value(byte_value)
+ }.to raise_error(ByteConverter::InvalidBytesError)
+ end
+ end
+ end
end
end
diff --git a/spec/unit/lib/cloud_controller/app_services/process_log_rate_limit_calculator_spec.rb b/spec/unit/lib/cloud_controller/app_services/process_log_rate_limit_calculator_spec.rb
new file mode 100644
index 00000000000..bfbd21af3f4
--- /dev/null
+++ b/spec/unit/lib/cloud_controller/app_services/process_log_rate_limit_calculator_spec.rb
@@ -0,0 +1,132 @@
+require 'spec_helper'
+
+module VCAP::CloudController
+ RSpec.describe ProcessLogRateLimitCalculator do
+ subject { ProcessLogRateLimitCalculator.new(process) }
+ let(:process_guid) { 'i-do-not-match-the-app-guid' }
+ let(:app_model) { AppModel.make }
+ let(:process) { ProcessModel.make(guid: process_guid, app: app_model) }
+ let(:stopped_state) { 'STOPPED' }
+ let(:started_state) { 'STARTED' }
+
+ describe '#additional_log_rate_limit_requested' do
+ context 'when the app state is STOPPED' do
+ before do
+ process.state = stopped_state
+ process.save(validate: false)
+ end
+
+ it 'returns 0' do
+ expect(subject.additional_log_rate_limit_requested).to eq(0)
+ end
+ end
+
+ context 'when the app state is STARTED' do
+ let(:process) { ProcessModel.make(state: started_state, guid: process_guid, app: app_model) }
+
+ context 'and the app is already in the db' do
+ it 'raises ApplicationMissing if the app no longer exists in the db' do
+ process.delete
+ expect { subject.additional_log_rate_limit_requested }.to raise_error(CloudController::Errors::ApplicationMissing)
+ end
+
+ context 'and it is changing from STOPPED' do
+ before do
+ db_app = ProcessModel.find(guid: process.guid)
+ db_app.state = stopped_state
+ db_app.save(validate: false)
+ end
+
+ it 'returns the total requested log_rate_limit' do
+ process.state = started_state
+ expect(subject.additional_log_rate_limit_requested).to eq(subject.total_requested_log_rate_limit)
+ end
+ end
+
+ context 'and the app is already STARTED' do
+ before do
+ db_app = ProcessModel.find(guid: process.guid)
+ db_app.state = started_state
+ db_app.save(validate: false)
+ end
+
+ it 'returns only newly requested log_rate_limit' do
+ expected = process.log_rate_limit
+ process.instances += 1
+
+ expect(subject.additional_log_rate_limit_requested).to eq(expected)
+ end
+ end
+ end
+
+ context 'and the app is new' do
+ let(:process) { ProcessModel.new }
+ before do
+ process.instances = 1
+ process.log_rate_limit = 100
+ end
+
+ it 'returns the total requested log_rate_limit' do
+ process.state = started_state
+ expect(subject.additional_log_rate_limit_requested).to eq(subject.total_requested_log_rate_limit)
+ end
+ end
+ end
+
+ context 'and the app is requesting unlimited log quota' do
+ let(:process) { ProcessModel.new }
+ before do
+ process.instances = 1
+ process.log_rate_limit = -1
+ end
+
+ it 'returns the fact that it is requesting unlimited quota' do
+ process.state = started_state
+ expect(subject.additional_log_rate_limit_requested).to eq(-1)
+ end
+ end
+ end
+
+ describe '#total_requested_log_rate_limit' do
+ it 'returns requested log_rate_limit * requested instances' do
+ expected = process.log_rate_limit * process.instances
+ expect(subject.total_requested_log_rate_limit).to eq(expected)
+ end
+ end
+
+ describe '#currently_used_log_rate_limit' do
+ context 'when the app is new' do
+ let(:process) { ProcessModel.new }
+ it 'returns 0' do
+ expect(subject.currently_used_log_rate_limit).to eq(0)
+ end
+ end
+
+ it 'raises ApplicationMissing if the app no longer exists in the db' do
+ process.delete
+ expect { subject.currently_used_log_rate_limit }.to raise_error(CloudController::Errors::ApplicationMissing)
+ end
+
+ context 'when the app in the db is STOPPED' do
+ it 'returns 0' do
+ expect(subject.currently_used_log_rate_limit).to eq(0)
+ end
+ end
+
+ context 'when the app in the db is STARTED' do
+ before do
+ process.state = started_state
+ process.save(validate: false)
+ end
+
+ it 'returns the log_rate_limit * instances of the db row' do
+ expected = process.instances * process.log_rate_limit
+ process.instances += 5
+ process.log_rate_limit += 100
+
+ expect(subject.currently_used_log_rate_limit).to eq(expected)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/lib/cloud_controller/backends/instances_reporters_spec.rb b/spec/unit/lib/cloud_controller/backends/instances_reporters_spec.rb
index 231e201cf33..40d33a94ebc 100644
--- a/spec/unit/lib/cloud_controller/backends/instances_reporters_spec.rb
+++ b/spec/unit/lib/cloud_controller/backends/instances_reporters_spec.rb
@@ -5,26 +5,20 @@ module VCAP::CloudController
subject(:instances_reporters) { InstancesReporters.new }
let(:bbs_instances_client) { instance_double(Diego::BbsInstancesClient) }
- let(:traffic_controller_client) { instance_double(::TrafficController::Client) }
let(:logcache_client) { instance_double(::Logcache::Client) }
- let(:tc_compatible_logcache_client) { instance_double(Logcache::TrafficControllerDecorator) }
+ let(:log_cache_metrics_client) { instance_double(Logcache::ContainerMetricBatcher) }
let(:diego_process) { ProcessModelFactory.make(diego: true) }
let(:diego_instances_reporter) { instance_double(Diego::InstancesReporter) }
let(:diego_instances_stats_reporter) { instance_double(Diego::InstancesStatsReporter) }
- let(:temporary_use_logcache) { false }
before do
- TestConfig.override(temporary_use_logcache: temporary_use_logcache)
-
CloudController::DependencyLocator.instance.register(:bbs_instances_client, bbs_instances_client)
- CloudController::DependencyLocator.instance.register(:traffic_controller_client, traffic_controller_client)
CloudController::DependencyLocator.instance.register(:logcache_client, logcache_client)
- CloudController::DependencyLocator.instance.register(:traffic_controller_compatible_logcache_client, tc_compatible_logcache_client)
+ CloudController::DependencyLocator.instance.register(:log_cache_metrics_client, log_cache_metrics_client)
allow(Diego::InstancesReporter).to receive(:new).with(bbs_instances_client).and_return(diego_instances_reporter)
- allow(Diego::InstancesStatsReporter).to receive(:new).with(bbs_instances_client, traffic_controller_client).and_return(diego_instances_stats_reporter)
- allow(Diego::InstancesStatsReporter).to receive(:new).with(bbs_instances_client, tc_compatible_logcache_client).and_return(diego_instances_stats_reporter)
+ allow(Diego::InstancesStatsReporter).to receive(:new).with(bbs_instances_client, log_cache_metrics_client).and_return(diego_instances_stats_reporter)
end
describe '#number_of_starting_and_running_instances_for_process' do
@@ -106,22 +100,9 @@ module VCAP::CloudController
allow(diego_instances_stats_reporter).to receive(:stats_for_app).with(app)
end
- context 'when temporary_use_logcache is true' do
- let(:temporary_use_logcache) { true }
-
- it 'uses the logcache' do
- instances_reporters.stats_for_app(app)
- expect(Diego::InstancesStatsReporter).to have_received(:new).with(bbs_instances_client, tc_compatible_logcache_client)
- end
- end
-
- context 'when temporary_use_logcache is false' do
- let(:temporary_use_logcache) { false }
-
- it 'uses the trafficcontroller' do
- instances_reporters.stats_for_app(app)
- expect(Diego::InstancesStatsReporter).to have_received(:new).with(bbs_instances_client, traffic_controller_client)
- end
+ it 'uses the logcache' do
+ instances_reporters.stats_for_app(app)
+ expect(Diego::InstancesStatsReporter).to have_received(:new).with(bbs_instances_client, log_cache_metrics_client)
end
end
end
diff --git a/spec/unit/lib/cloud_controller/backends/quota_validating_staging_log_rate_limit_calculator_spec.rb b/spec/unit/lib/cloud_controller/backends/quota_validating_staging_log_rate_limit_calculator_spec.rb
new file mode 100644
index 00000000000..c24e9af1c49
--- /dev/null
+++ b/spec/unit/lib/cloud_controller/backends/quota_validating_staging_log_rate_limit_calculator_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+require 'cloud_controller/backends/quota_validating_staging_log_rate_limit_calculator'
+
+module VCAP::CloudController
+ RSpec.describe QuotaValidatingStagingLogRateLimitCalculator do
+ let(:calculator) { QuotaValidatingStagingLogRateLimitCalculator.new }
+
+ describe '#get_limit' do
+ let(:space_quota_limit) { 200 }
+ let(:org_quota_limit) { 200 }
+ let(:requested_limit) { 100 }
+ let(:space) { Space.make }
+ let(:org) { space.organization }
+ let(:space_quota_definition) { SpaceQuotaDefinition.make(organization: org, log_rate_limit: space_quota_limit) }
+ let(:quota_definition) { QuotaDefinition.make(log_rate_limit: org_quota_limit) }
+
+ before do
+ space.space_quota_definition = space_quota_definition
+ org.quota_definition = quota_definition
+ space.save
+ org.save
+ end
+
+ it 'uses the requested_limit' do
+ limit = calculator.get_limit(requested_limit, space, org)
+ expect(limit).to eq(requested_limit)
+ end
+
+ context 'when the requested_limit is passed as an integer string' do
+ let(:requested_limit) { '100' }
+
+ it 'uses the requested_limit' do
+ limit = calculator.get_limit(requested_limit, space, org)
+ expect(limit).to eq(100)
+ end
+ end
+
+ context 'when the requested_limit exceeds the space quota' do
+ let(:space_quota_limit) { requested_limit - 1 }
+
+ it 'raises a SpaceQuotaExceeded error' do
+ expect {
+ calculator.get_limit(requested_limit, space, org)
+ }.to raise_error(QuotaValidatingStagingLogRateLimitCalculator::SpaceQuotaExceeded, /staging requires 100 bytes per second/)
+ end
+ end
+
+ context 'when the requested_limit exceeds the org quota' do
+ let(:org_quota_limit) { requested_limit - 1 }
+
+ it 'raises a OrgQuotaExceeded error' do
+ expect {
+ calculator.get_limit(requested_limit, space, org)
+ }.to raise_error(QuotaValidatingStagingLogRateLimitCalculator::OrgQuotaExceeded, /staging requires 100 bytes per second/)
+ end
+ end
+
+ context 'when the requested_limit is nil' do
+ let(:requested_limit) { nil }
+
+ it 'uses no limit' do
+ limit = calculator.get_limit(requested_limit, space, org)
+ expect(limit).to eq(-1)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/lib/cloud_controller/dependency_locator_spec.rb b/spec/unit/lib/cloud_controller/dependency_locator_spec.rb
index 1728e1d515e..297f0ecc500 100644
--- a/spec/unit/lib/cloud_controller/dependency_locator_spec.rb
+++ b/spec/unit/lib/cloud_controller/dependency_locator_spec.rb
@@ -477,13 +477,7 @@
end
end
- context '#traffic_controller_client' do
- it 'returns the tc client' do
- expect(locator.traffic_controller_client).to be_an_instance_of(TrafficController::Client)
- end
- end
-
- describe '#traffic_controller_compatible_logcache_client' do
+ describe '#log_cache_metrics_client' do
let(:logcache_client) { instance_double(Logcache::Client) }
before do
allow(Logcache::Client).to receive(:new).and_return(logcache_client)
@@ -493,7 +487,7 @@
TestConfig.override(
logcache_tls: nil
)
- expect(locator.traffic_controller_compatible_logcache_client).to be_an_instance_of(Logcache::TrafficControllerDecorator)
+ expect(locator.log_cache_metrics_client).to be_an_instance_of(Logcache::ContainerMetricBatcher)
expect(Logcache::Client).to have_received(:new).with(
host: 'http://doppler.service.cf.internal',
port: 8080,
@@ -517,7 +511,7 @@
subject_name: 'some-tls-cert-san'
}
)
- expect(locator.traffic_controller_compatible_logcache_client).to be_an_instance_of(Logcache::TrafficControllerDecorator)
+ expect(locator.log_cache_metrics_client).to be_an_instance_of(Logcache::ContainerMetricBatcher)
expect(Logcache::Client).to have_received(:new).with(
host: 'some-logcache-host',
port: 1234,
diff --git a/spec/unit/lib/cloud_controller/diego/app_recipe_builder_spec.rb b/spec/unit/lib/cloud_controller/diego/app_recipe_builder_spec.rb
index 9f0e3a347c7..872264c4100 100644
--- a/spec/unit/lib/cloud_controller/diego/app_recipe_builder_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/app_recipe_builder_spec.rb
@@ -47,6 +47,7 @@ module Diego
instances: 21,
memory: 128,
disk_quota: 256,
+ log_rate_limit: 1024,
command: command,
file_descriptors: 32,
health_check_type: 'port',
@@ -285,6 +286,7 @@ module Diego
expect(lrp.log_source).to eq(LRP_LOG_SOURCE)
expect(lrp.max_pids).to eq(100)
expect(lrp.memory_mb).to eq(128)
+ expect(lrp.log_rate_limit.bytes_per_second).to eq(1024)
expect(lrp.metrics_guid).to eq(process.app.guid)
expect(lrp.metric_tags.keys.size).to eq(11)
@@ -872,6 +874,7 @@ module Diego
expect(lrp.log_guid).to eq(process.app.guid)
expect(lrp.max_pids).to eq(100)
expect(lrp.memory_mb).to eq(128)
+ expect(lrp.log_rate_limit.bytes_per_second).to eq(1024)
expect(lrp.metrics_guid).to eq(process.app.guid)
expect(lrp.metric_tags.keys.size).to eq(11)
diff --git a/spec/unit/lib/cloud_controller/diego/reporters/instances_stats_reporter_spec.rb b/spec/unit/lib/cloud_controller/diego/reporters/instances_stats_reporter_spec.rb
index 5fe14ae4baa..2cda59e841e 100644
--- a/spec/unit/lib/cloud_controller/diego/reporters/instances_stats_reporter_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/reporters/instances_stats_reporter_spec.rb
@@ -4,12 +4,12 @@
module VCAP::CloudController
module Diego
RSpec.describe InstancesStatsReporter do
- subject(:instances_reporter) { InstancesStatsReporter.new(bbs_instances_client, traffic_controller_client) }
+ subject(:instances_reporter) { InstancesStatsReporter.new(bbs_instances_client, log_cache_client) }
let(:app) { AppModel.make }
let(:process) { ProcessModel.make(instances: desired_instances, app: app) }
let(:desired_instances) { 1 }
let(:bbs_instances_client) { instance_double(BbsInstancesClient) }
- let(:traffic_controller_client) { instance_double(TrafficController::Client) }
+ let(:log_cache_client) { instance_double(Logcache::ContainerMetricBatcher) }
let(:two_days_ago_since_epoch_ns) { 2.days.ago.to_f * 1e9 }
let(:two_days_in_seconds) { 60 * 60 * 24 * 2 }
@@ -61,22 +61,17 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
actual_lrp.actual_lrp_net_info = lrp_1_net_info
end
end
- let(:traffic_controller_response) do
- [
- ::TrafficController::Models::Envelope.new(
- origin: 'does-anyone-even-know?',
- eventType: ::TrafficController::Models::Envelope::EventType::ContainerMetric,
- containerMetric: ::TrafficController::Models::ContainerMetric.new(
- instanceIndex: 0,
- cpuPercentage: 3.92,
- memoryBytes: 564,
- diskBytes: 5000,
- memoryBytesQuota: 1234,
- diskBytesQuota: 10234,
- ),
- tags: [::TrafficController::Models::Envelope::TagsEntry.new(key: 'process_id', value: process.guid)],
- ),
- ]
+ let(:log_cache_response) do
+ container_metric_batch = ::Logcache::ContainerMetricBatch.new
+ container_metric_batch.instance_index = 0
+ container_metric_batch.cpu_percentage = 3.92
+ container_metric_batch.memory_bytes = 564
+ container_metric_batch.disk_bytes = 5000
+ container_metric_batch.memory_bytes_quota = 1234
+ container_metric_batch.disk_bytes_quota = 10234
+ container_metric_batch.log_rate = 5
+ container_metric_batch.log_rate_limit = 10
+ [container_metric_batch]
end
let(:expected_stats_response) do
@@ -93,12 +88,14 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
uptime: two_days_in_seconds,
mem_quota: 1234,
disk_quota: 10234,
+ log_rate_limit: 10,
fds_quota: process.file_descriptors,
usage: {
time: formatted_current_time,
cpu: 0.0392,
mem: 564,
disk: 5000,
+ log_rate: 5,
}
},
details: 'some-details',
@@ -109,9 +106,9 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
before do
allow(bbs_instances_client).to receive(:lrp_instances).and_return(bbs_actual_lrps_response)
allow(bbs_instances_client).to receive(:desired_lrp_instance).and_return(bbs_desired_lrp_response)
- allow(traffic_controller_client).to receive(:container_metrics).
+ allow(log_cache_client).to receive(:container_metrics).
with(auth_token: 'my-token', source_guid: process.app.guid, logcache_filter: anything).
- and_return(traffic_controller_response)
+ and_return(log_cache_response)
allow(VCAP::CloudController::SecurityContext).to receive(:auth_token).and_return('my-token')
end
@@ -122,9 +119,9 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
it 'passes a process_id filter' do
filter = nil
- allow(traffic_controller_client).to receive(:container_metrics) { |args|
+ allow(log_cache_client).to receive(:container_metrics) { |args|
filter = args[:logcache_filter]
- }.and_return(traffic_controller_response)
+ }.and_return(log_cache_response)
expected_envelope = Loggregator::V2::Envelope.new(
source_id: process.app.guid,
@@ -162,28 +159,24 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
'source_id' => ::Diego::Bbs::Models::MetricTagValue.new(static: process.guid),
}
}
- let(:traffic_controller_response) do
- [
- ::TrafficController::Models::Envelope.new(
- origin: 'does-anyone-even-know?',
- eventType: ::TrafficController::Models::Envelope::EventType::ContainerMetric,
- containerMetric: ::TrafficController::Models::ContainerMetric.new(
- instanceIndex: 0,
- cpuPercentage: 3.92,
- memoryBytes: 564,
- diskBytes: 5000,
- memoryBytesQuota: 1234,
- diskBytesQuota: 10234,
- ),
- ),
- ]
+ let(:log_cache_response) do
+ container_metric_batch = ::Logcache::ContainerMetricBatch.new
+ container_metric_batch.instance_index = 0
+ container_metric_batch.cpu_percentage = 3.92
+ container_metric_batch.memory_bytes = 564
+ container_metric_batch.disk_bytes = 5000
+ container_metric_batch.log_rate = 5
+ container_metric_batch.memory_bytes_quota = 1234
+ container_metric_batch.disk_bytes_quota = 10234
+ container_metric_batch.log_rate_limit = 10
+ [container_metric_batch]
end
it 'gets metrics for the process and does not filter on the source_id' do
- expect(traffic_controller_client).
+ expect(log_cache_client).
to receive(:container_metrics).
with(auth_token: 'my-token', source_guid: process.guid, logcache_filter: anything).
- and_return(traffic_controller_response)
+ and_return(log_cache_response)
expect(instances_reporter.stats_for_app(process)).to eq([expected_stats_response, []])
end
@@ -215,19 +208,13 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
end
end
- context 'when traffic controller somehow returns a partial response without cpuPercentage' do
+ context 'when log cache somehow returns a partial response without cpu_percentage' do
# We aren't exactly sure how this happens, but it can happen on an overloaded deployment, see #156707836
- let(:traffic_controller_response) do
- [
- ::TrafficController::Models::Envelope.new(
- origin: 'does-anyone-even-know?',
- eventType: ::TrafficController::Models::Envelope::EventType::ContainerMetric,
- containerMetric: ::TrafficController::Models::ContainerMetric.new(
- instanceIndex: 0,
- memoryBytes: 564,
- ),
- ),
- ]
+ let(:log_cache_response) do
+ container_metric_batch = ::Logcache::ContainerMetricBatch.new
+ container_metric_batch.instance_index = 0
+ container_metric_batch.memory_bytes = 564
+ [container_metric_batch]
end
it 'sets all the stats to zero' do
@@ -237,6 +224,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
cpu: 0,
mem: 0,
disk: 0,
+ log_rate: 0,
})
end
end
@@ -278,6 +266,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
uptime: two_days_in_seconds,
mem_quota: nil,
disk_quota: nil,
+ log_rate_limit: nil,
fds_quota: process.file_descriptors,
usage: {}
},
@@ -287,7 +276,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
end
before do
- allow(traffic_controller_client).to receive(:container_metrics).and_raise(error)
+ allow(log_cache_client).to receive(:container_metrics).and_raise(error)
allow(instances_reporter).to receive(:logger).and_return(mock_logger)
end
@@ -316,7 +305,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
context 'when number of actual lrps > desired number of instances' do
let(:desired_instances) { 0 }
- let(:traffic_controller_response) { [] }
+ let(:log_cache_response) { [] }
it 'ignores superfluous instances' do
expect(instances_reporter.stats_for_app(process)).to eq([{}, []])
@@ -324,7 +313,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
end
context 'when number of container metrics < desired number of instances' do
- let(:traffic_controller_response) { [] }
+ let(:log_cache_response) { [] }
it 'provides defaults for unreported instances' do
result, _ = instances_reporter.stats_for_app(process)
@@ -334,6 +323,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
cpu: 0,
mem: 0,
disk: 0,
+ log_rate: 0,
})
end
end
@@ -360,11 +350,11 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
end
end
- context 'when an error is raised communicating with traffic controller' do
+ context 'when an error is raised communicating with log cache' do
let(:error) { StandardError.new('tomato') }
let(:mock_logger) { double(:logger, error: nil, debug: nil) }
before do
- allow(traffic_controller_client).to receive(:container_metrics).
+ allow(log_cache_client).to receive(:container_metrics).
with(auth_token: 'my-token', source_guid: process.app.guid, logcache_filter: anything).
and_raise(error)
allow(instances_reporter).to receive(:logger).and_return(mock_logger)
@@ -402,6 +392,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
uptime: two_days_in_seconds,
mem_quota: nil,
disk_quota: nil,
+ log_rate_limit: nil,
fds_quota: process.file_descriptors,
usage: {}
},
@@ -411,7 +402,7 @@ def make_actual_lrp(instance_guid:, index:, state:, error:, since:)
end
before do
- allow(traffic_controller_client).to receive(:container_metrics).
+ allow(log_cache_client).to receive(:container_metrics).
with(auth_token: 'my-token', source_guid: process.app.guid, logcache_filter: anything).
and_raise(error)
allow(instances_reporter).to receive(:logger).and_return(mock_logger)
diff --git a/spec/unit/lib/cloud_controller/diego/task_recipe_builder_spec.rb b/spec/unit/lib/cloud_controller/diego/task_recipe_builder_spec.rb
index f7b8f1acb8b..8d5315f5ef1 100644
--- a/spec/unit/lib/cloud_controller/diego/task_recipe_builder_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/task_recipe_builder_spec.rb
@@ -14,6 +14,7 @@ module Diego
details.environment_variables = [::Diego::Bbs::Models::EnvironmentVariable.new(name: 'nightshade_fruit', value: 'potato')]
details.staging_memory_in_mb = 42
details.staging_disk_in_mb = 51
+ details.staging_log_rate_limit_bytes_per_second = 67
details.start_after_staging = true
details.lifecycle = lifecycle
details.isolation_segment = isolation_segment
@@ -152,6 +153,7 @@ module Diego
expect(result.memory_mb).to eq(42)
expect(result.disk_mb).to eq(51)
+ expect(result.log_rate_limit.bytes_per_second).to eq(67)
expect(result.image_layers).to eq(lifecycle_image_layers)
expect(result.cpu_weight).to eq(50)
@@ -344,6 +346,7 @@ module Diego
app: app,
disk_in_mb: 1024,
memory_in_mb: 2048,
+ log_rate_limit: 3072,
sequence_id: 9
)
end
@@ -468,6 +471,7 @@ module Diego
expect(result.log_guid).to eq(app.guid)
expect(result.memory_mb).to eq(2048)
expect(result.disk_mb).to eq(1024)
+ expect(result.log_rate_limit.bytes_per_second).to eq(3072)
expect(result.environment_variables).to eq(lifecycle_environment_variables)
expect(result.legacy_download_user).to eq('vcap')
expect(result.root_fs).to eq('preloaded:potato-stack')
@@ -610,6 +614,7 @@ module Diego
expect(result.disk_mb).to eq(1024)
expect(result.memory_mb).to eq(2048)
+ expect(result.log_rate_limit.bytes_per_second).to eq(3072)
expect(result.log_guid).to eq(app.guid)
expect(result.privileged).to be(false)
expect(result.egress_rules).to eq([
diff --git a/spec/unit/lib/utils/multipart_parser_wrapper_spec.rb b/spec/unit/lib/utils/multipart_parser_wrapper_spec.rb
deleted file mode 100644
index fa711e9c9be..00000000000
--- a/spec/unit/lib/utils/multipart_parser_wrapper_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe VCAP::MultipartParserWrapper do
- subject(:parser) { VCAP::MultipartParserWrapper.new(body: body, boundary: boundary) }
- describe '#next_part' do
- let(:body) do
- [
- "--#{boundary}",
- "\r\n",
- "\r\n",
- first_part,
- "\r\n",
- "--#{boundary}",
- "\r\n",
- "\r\n",
- second_part,
- "\r\n",
- "--#{boundary}--",
- ].join
- end
- let(:boundary) { 'boundary-guid' }
- let(:first_part) { "part one\r\n data" }
- let(:second_part) { 'part two data' }
-
- it 'can return the first part' do
- expect(parser.next_part).to eq("part one\r\n data")
- end
-
- it 'can read more than one part' do
- expect(parser.next_part).to eq("part one\r\n data")
- expect(parser.next_part).to eq('part two data')
- end
-
- context 'when there are no parts left' do
- it 'returns nil' do
- expect(parser.next_part).to eq("part one\r\n data")
- expect(parser.next_part).to eq('part two data')
- expect(parser.next_part).to be_nil
- end
- end
-
- context 'when there body contains no parts' do
- let(:body) { "\r\n--#{boundary}--\r\n" }
- it 'returns nil' do
- expect(parser.next_part).to be_nil
- end
- end
-
- context 'when the body is empty' do
- let(:body) { '' }
-
- it 'returns nil' do
- expect(parser.next_part).to be_nil
- end
- end
-
- context 'when the body is not a valid multipart response' do
- let(:body) { 'potato' }
-
- it 'returns nil' do
- expect(parser.next_part).to be_nil
- end
- end
- end
-end
diff --git a/spec/unit/messages/app_manifest_message_spec.rb b/spec/unit/messages/app_manifest_message_spec.rb
index caf0995f231..7d13737b181 100644
--- a/spec/unit/messages/app_manifest_message_spec.rb
+++ b/spec/unit/messages/app_manifest_message_spec.rb
@@ -79,6 +79,20 @@ module VCAP::CloudController
end
end
+ context 'when disk_quota is zero' do
+ let(:params_from_yaml) { { name: 'eugene', disk_quota: 0 } }
+
+ it 'is not valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+
+ expect(message).not_to be_valid
+ expect(message.errors).to have(1).items
+ expect(message.errors.full_messages).to include(
+ 'Process "web": Disk quota must use a supported unit: B, K, KB, M, MB, G, GB, T, or TB'
+ )
+ end
+ end
+
context 'when disk_quota is not a positive amount' do
let(:params_from_yaml) { { name: 'eugene', disk_quota: '-1MB' } }
@@ -104,6 +118,130 @@ module VCAP::CloudController
end
end
+ describe 'log-rate-limit-per-second' do
+ context 'when log-rate-limit-per-second unit is not part of expected set of values' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: '200INVALID' } }
+
+ it 'is not valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+
+ expect(message).not_to be_valid
+ expect(message.errors).to have(1).items
+ expect(message.errors.full_messages).to include(
+ 'Process "web": Log rate limit per second must use a supported unit: B, K, KB, M, MB, G, GB, T, or TB'
+ )
+ end
+ end
+
+ context 'when log-rate-limit-per-second is less than -1 bytes' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: '-1M' } }
+
+ it 'is not valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+
+ expect(message).not_to be_valid
+ expect(message.errors).to have(1).items
+ expect(message.errors.full_messages).to include('Process "web": Log rate limit must be an integer greater than or equal to -1')
+ end
+ end
+
+ context 'when log-rate-limit-per-second is not numeric' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: 'gerg herscheisers' } }
+
+ it 'is not valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+
+ expect(message).not_to be_valid
+ expect(message.errors).to have(1).items
+ expect(message.errors.full_messages).to include(
+ 'Process "web": Log rate limit per second is not a number'
+ )
+ end
+ end
+
+ context 'when log-rate-limit-per-second is an unlimited amount' do
+ context 'specified as -1' do
+ let(:params_from_yaml) { { name: 'eugene', 'log-rate-limit-per-second' => '-1' } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ end
+ end
+
+ context 'specified as the number -1' do
+ let(:params_from_yaml) { { name: 'eugene', 'log-rate-limit-per-second' => -1 } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ end
+ end
+
+ context 'with a bytes suffix' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: '-1B' } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ end
+ end
+ end
+
+ context 'when log-rate-limit-per-second is in megabytes' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: '1MB' } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ end
+ end
+
+ context 'when log-rate-limit-per-second is 0' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: '0' } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ expect(message.manifest_process_scale_messages.first.log_rate_limit).to eq(0)
+ end
+ end
+
+ context 'when log-rate-limit-per-second is the number 0' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: 0 } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ expect(message.manifest_process_scale_messages.first.log_rate_limit).to eq(0)
+ end
+ end
+
+ context 'when log-rate-limit-per-second is 0TB' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: '0TB' } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ expect(message.manifest_process_scale_messages.first.log_rate_limit).to eq(0)
+ end
+ end
+
+ context 'when log-rate-limit-per-second is a positive integer without a suffix' do
+ let(:params_from_yaml) { { name: 'eugene', log_rate_limit_per_second: 9999 } }
+
+ it 'is not valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+
+ expect(message).not_to be_valid
+ expect(message.errors).to have(1).items
+ expect(message.errors.full_messages).to include(
+ 'Process "web": Log rate limit per second must use a supported unit: B, K, KB, M, MB, G, GB, T, or TB'
+ )
+ end
+ end
+ end
+
describe 'buildpack' do
context 'when providing a valid buildpack name' do
let(:buildpack) { Buildpack.make }
@@ -572,6 +710,7 @@ module VCAP::CloudController
'instances' => -30,
'memory' => 'potato',
'disk_quota' => '100',
+ 'log-rate-limit-per-second' => 'kumara',
'health_check_type' => 'sweet_potato',
'health_check_http_endpoint' => '/healthcheck_potato',
'health_check_invocation_timeout' => 'yucca',
@@ -585,6 +724,7 @@ module VCAP::CloudController
'instances' => 'cassava',
'memory' => 'potato',
'disk_quota' => '100',
+ 'log-rate-limit-per-second' => '100',
'health_check_type' => 'sweet_potato',
'health_check_http_endpoint' => '/healthcheck_potato',
'health_check_invocation_timeout' => 'yucca',
@@ -602,10 +742,11 @@ module VCAP::CloudController
it 'includes the type of the process in the error message' do
message = AppManifestMessage.create_from_yml(params_from_yaml)
expect(message).to_not be_valid
- expect(message.errors).to have(16).items
+ expect(message.errors).to have(18).items
expect(message.errors.full_messages).to match_array([
'Process "type1": Command must be between 1 and 4096 characters',
'Process "type1": Disk quota must use a supported unit: B, K, KB, M, MB, G, GB, T, or TB',
+ 'Process "type1": Log rate limit per second is not a number',
'Process "type1": Instances must be greater than or equal to 0',
'Process "type1": Memory is not a number',
'Process "type1": Timeout is not a number',
@@ -614,6 +755,7 @@ module VCAP::CloudController
'Process "type1": Health check invocation timeout is not a number',
'Process "type2": Command must be between 1 and 4096 characters',
'Process "type2": Disk quota must use a supported unit: B, K, KB, M, MB, G, GB, T, or TB',
+ 'Process "type2": Log rate limit per second must use a supported unit: B, K, KB, M, MB, G, GB, T, or TB',
'Process "type2": Instances is not a number',
'Process "type2": Memory is not a number',
'Process "type2": Timeout is not a number',
@@ -639,6 +781,28 @@ module VCAP::CloudController
expect(message.errors.full_messages).to include('Process "bob" may only be present once')
end
end
+
+ context 'log-rate-limit-per-second' do
+ context 'when log-rate-limit-per-second is an unlimited amount' do
+ context 'specified as -1' do
+ let(:params_from_yaml) { { name: 'eugene', processes: [{ 'type' => 'foo', 'log-rate-limit-per-second' => '-1' }] } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ expect(message.manifest_process_scale_messages.first.log_rate_limit).to eq(-1)
+ end
+ end
+ context 'with a bytes suffix' do
+ let(:params_from_yaml) { { name: 'eugene', processes: [{ 'type' => 'foo', 'log-rate-limit-per-second' => '-1B' }] } }
+
+ it 'is valid' do
+ message = AppManifestMessage.create_from_yml(params_from_yaml)
+ expect(message).to be_valid
+ end
+ end
+ end
+ end
end
describe 'sidecars' do
diff --git a/spec/unit/messages/build_create_message_spec.rb b/spec/unit/messages/build_create_message_spec.rb
index e3e46df6210..dae6f928f95 100644
--- a/spec/unit/messages/build_create_message_spec.rb
+++ b/spec/unit/messages/build_create_message_spec.rb
@@ -211,6 +211,47 @@ module VCAP::CloudController
end
end
+ describe '#staging_log_rate_limit_bytes_per_second' do
+ subject(:build_create_message) { BuildCreateMessage.new(params) }
+ let(:params) { { staging_log_rate_limit_bytes_per_second: -1 } }
+
+ it 'returns the staging_log_rate_limit_bytes_per_second' do
+ expect(build_create_message.staging_log_rate_limit_bytes_per_second).to eq(-1)
+ end
+
+ context 'when not provided' do
+ let(:params) { nil }
+
+ it 'returns nil' do
+ expect(build_create_message.staging_log_rate_limit_bytes_per_second).to eq(nil)
+ end
+ end
+
+ context 'when the value is less than -1' do
+ let(:params) do
+ {
+ package: { guid: 'some-guid' },
+ staging_log_rate_limit_bytes_per_second: -2
+ }
+ end
+ it 'is invalid' do
+ expect(build_create_message.valid?).to be false
+ end
+ end
+
+ context 'when the value is too large' do
+ let(:params) do
+ {
+ package: { guid: 'some-guid' },
+ staging_log_rate_limit_bytes_per_second: 2**63
+ }
+ end
+ it 'is invalid' do
+ expect(build_create_message.valid?).to be false
+ end
+ end
+ end
+
describe '#environment variables' do
subject(:build_create_message) { BuildCreateMessage.new(params) }
diff --git a/spec/unit/messages/manifest_process_scale_message_spec.rb b/spec/unit/messages/manifest_process_scale_message_spec.rb
index 059924ba94c..7831ed9e1dd 100644
--- a/spec/unit/messages/manifest_process_scale_message_spec.rb
+++ b/spec/unit/messages/manifest_process_scale_message_spec.rb
@@ -141,6 +141,56 @@ module VCAP::CloudController
end
end
+ describe '#log_rate_limit' do
+ context 'when log_rate_limit is not an number' do
+ let(:params) { { log_rate_limit: 'silly string thing' } }
+
+ it 'is not valid' do
+ message = ManifestProcessScaleMessage.new(params)
+
+ expect(message).not_to be_valid
+ expect(message.errors.count).to eq(1)
+ expect(message.errors.full_messages).to include('Log rate limit must be an integer greater than or equal to -1')
+ end
+ end
+
+ context 'when log_rate_limit is < -1' do
+ let(:params) { { log_rate_limit: -2 } }
+
+ it 'is not valid' do
+ message = ManifestProcessScaleMessage.new(params)
+
+ expect(message).not_to be_valid
+ expect(message.errors.count).to eq(1)
+ expect(message.errors.full_messages).to include('Log rate limit must be an integer greater than or equal to -1')
+ end
+ end
+
+ context 'when log_rate_limit is not an integer' do
+ let(:params) { { log_rate_limit: 3.5 } }
+
+ it 'is not valid' do
+ message = ManifestProcessScaleMessage.new(params)
+
+ expect(message).not_to be_valid
+ expect(message.errors.count).to eq(1)
+ expect(message.errors.full_messages).to include('Log rate limit must be an integer greater than or equal to -1')
+ end
+ end
+
+ context 'when log_rate_limit is too large' do
+ let(:params) { { log_rate_limit: 2**63 } }
+
+ it 'is not valid' do
+ message = ManifestProcessScaleMessage.new(params)
+
+ expect(message).not_to be_valid
+ expect(message.errors.count).to eq(1)
+ expect(message.errors.full_messages).to include('Log rate limit must be an integer greater than or equal to -1')
+ end
+ end
+ end
+
context 'when we have more than one error' do
let(:params) { { disk_quota: 3.5, memory: 'smiling greg' } }
@@ -161,7 +211,7 @@ module VCAP::CloudController
let(:manifest_message) { ManifestProcessScaleMessage.new(params) }
context 'when all params are given' do
- let(:params) { { instances: 3, memory: 1024, disk_quota: 2048 } }
+ let(:params) { { instances: 3, memory: 1024, disk_quota: 2048, log_rate_limit: 1024 } }
it 'returns a process_scale_message with the appropriate values' do
scale_message = manifest_message.to_process_scale_message
@@ -169,6 +219,7 @@ module VCAP::CloudController
expect(scale_message.instances).to eq(3)
expect(scale_message.memory_in_mb).to eq(1024)
expect(scale_message.disk_in_mb).to eq(2048)
+ expect(scale_message.log_rate_limit_in_bytes_per_second).to eq(1024)
end
end
@@ -184,6 +235,16 @@ module VCAP::CloudController
end
end
+ context 'when no log_rate_limit is given' do
+ let(:params) { { instances: 3, memory: 1024 } }
+
+ it 'does not set anything for log_rate_limit_in_bytes_per_second' do
+ scale_message = manifest_message.to_process_scale_message
+
+ expect(scale_message.log_rate_limit_in_bytes_per_second).to be_falsey
+ end
+ end
+
context 'when no instances is given' do
let(:params) { { memory: 1024, disk_quota: 2048 } }
diff --git a/spec/unit/messages/organization_quotas_create_message_spec.rb b/spec/unit/messages/organization_quotas_create_message_spec.rb
index 38122e4c0af..5d183fe1c8a 100644
--- a/spec/unit/messages/organization_quotas_create_message_spec.rb
+++ b/spec/unit/messages/organization_quotas_create_message_spec.rb
@@ -240,6 +240,74 @@ module VCAP::CloudController
end
end
+ describe 'log_rate_limit_in_bytes_per_second' do
+ context 'when the type is a string' do
+ let(:params) {
+ {
+ name: 'my-name',
+ apps: { log_rate_limit_in_bytes_per_second: 'bob' },
+ relationships: relationships,
+ }
+ }
+
+ it 'is not valid' do
+ expect(subject).to be_invalid
+ expect(subject.errors[:apps]).to contain_exactly('Log rate limit in bytes per second is not a number')
+ end
+ end
+ context 'when the type is decimal' do
+ let(:params) {
+ {
+ name: 'my-name',
+ apps: { log_rate_limit_in_bytes_per_second: 1.1 },
+ relationships: relationships,
+ }
+ }
+
+ it 'is not valid' do
+ expect(subject).to be_invalid
+ expect(subject.errors[:apps]).to contain_exactly('Log rate limit in bytes per second must be an integer')
+ end
+ end
+ context 'when the type is a negative integer' do
+ let(:params) {
+ {
+ name: 'my-name',
+ apps: { log_rate_limit_in_bytes_per_second: -1 },
+ relationships: relationships,
+ }
+ }
+
+ it 'is not valid because "unlimited" is set with null, not -1, in V3' do
+ expect(subject).to be_invalid
+ expect(subject.errors[:apps]).to contain_exactly('Log rate limit in bytes per second must be greater than or equal to 0')
+ end
+ end
+
+ context 'when the type is zero' do
+ let(:params) {
+ {
+ name: 'my-name',
+ apps: { log_rate_limit_in_bytes_per_second: 0 },
+ relationships: relationships,
+ }
+ }
+
+ it { is_expected.to be_valid }
+ end
+ context 'when the type is nil (unlimited)' do
+ let(:params) {
+ {
+ name: 'my-name',
+ apps: { log_rate_limit_in_bytes_per_second: nil },
+ relationships: relationships,
+ }
+ }
+
+ it { is_expected.to be_valid }
+ end
+ end
+
describe 'total_instances' do
context 'when the type is a string' do
let(:params) {
diff --git a/spec/unit/messages/process_scale_message_spec.rb b/spec/unit/messages/process_scale_message_spec.rb
index 8de05bbf7d4..0a45eeda210 100644
--- a/spec/unit/messages/process_scale_message_spec.rb
+++ b/spec/unit/messages/process_scale_message_spec.rb
@@ -158,5 +158,53 @@ module VCAP::CloudController
expect(message.errors[:disk_in_mb]).to include('must be an integer')
end
end
+
+ context 'when log_rate_limit_in_bytes_per_second is not an number' do
+ let(:params) { { log_rate_limit_in_bytes_per_second: 'silly string thing' } }
+
+ it 'is not valid' do
+ message = ProcessScaleMessage.new(params)
+
+ expect(message).not_to be_valid
+ expect(message.errors.count).to eq(1)
+ expect(message.errors[:log_rate_limit_in_bytes_per_second]).to include('is not a number')
+ end
+ end
+
+ context 'when log_rate_limit_in_bytes_per_second is < -1' do
+ let(:params) { { log_rate_limit_in_bytes_per_second: -2 } }
+
+ it 'is not valid' do
+ message = ProcessScaleMessage.new(params)
+
+ expect(message).not_to be_valid
+ expect(message.errors.count).to eq(1)
+ expect(message.errors[:log_rate_limit_in_bytes_per_second]).to include('must be greater than or equal to -1')
+ end
+ end
+
+ context 'when log_rate_limit_in_bytes_per_second is > the max value allowed in the database' do
+ let(:params) { { log_rate_limit_in_bytes_per_second: BaseMessage::MAX_DB_BIGINT + 1 } }
+
+ it 'is not valid' do
+ message = ProcessScaleMessage.new(params)
+
+ expect(message).not_to be_valid
+ expect(message.errors.count).to eq(1)
+ expect(message.errors[:log_rate_limit_in_bytes_per_second]).to include('must be less than or equal to 9223372036854775807')
+ end
+ end
+
+ context 'when log_rate_limit_in_bytes_per_second is not an integer' do
+ let(:params) { { log_rate_limit_in_bytes_per_second: 3.5 } }
+
+ it 'is not valid' do
+ message = ProcessScaleMessage.new(params)
+
+ expect(message).not_to be_valid
+ expect(message.errors.count).to eq(1)
+ expect(message.errors[:log_rate_limit_in_bytes_per_second]).to include('must be an integer')
+ end
+ end
end
end
diff --git a/spec/unit/messages/quotas_apps_message_spec.rb b/spec/unit/messages/quotas_apps_message_spec.rb
index 77e215314c5..6b3caff77a3 100644
--- a/spec/unit/messages/quotas_apps_message_spec.rb
+++ b/spec/unit/messages/quotas_apps_message_spec.rb
@@ -256,6 +256,68 @@ module VCAP::CloudController
end
end
end
+
+ describe 'log_rate_limit_in_bytes_per_second' do
+ context 'when the type is a string' do
+ let(:params) do
+ { log_rate_limit_in_bytes_per_second: 'bob' }
+ end
+
+ it 'is not valid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to contain_exactly('Log rate limit in bytes per second is not a number')
+ end
+ end
+
+ context 'when the type is decimal' do
+ let(:params) do
+ { log_rate_limit_in_bytes_per_second: 1.1 }
+ end
+
+ it 'is not valid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to contain_exactly('Log rate limit in bytes per second must be an integer')
+ end
+ end
+
+ context 'when the type is a negative integer' do
+ let(:params) do
+ { log_rate_limit_in_bytes_per_second: -1 }
+ end
+
+ it 'is not valid because "unlimited" is set with null, not -1, in V3' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to contain_exactly('Log rate limit in bytes per second must be greater than or equal to 0')
+ end
+ end
+
+ context 'when the type is zero' do
+ let(:params) do
+ { log_rate_limit_in_bytes_per_second: 0 }
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when the type is nil (unlimited)' do
+ let(:params) do
+ { log_rate_limit_in_bytes_per_second: nil }
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when the value is greater than the maximum allowed value in the DB' do
+ let(:params) do
+ { log_rate_limit_in_bytes_per_second: 2**64 }
+ end
+
+ it 'is not valid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to contain_exactly('Log rate limit in bytes per second must be less than or equal to 9223372036854775807')
+ end
+ end
+ end
end
end
end
diff --git a/spec/unit/messages/space_quota_update_message_spec.rb b/spec/unit/messages/space_quota_update_message_spec.rb
index bd0cba290cf..d8b0adaccd6 100644
--- a/spec/unit/messages/space_quota_update_message_spec.rb
+++ b/spec/unit/messages/space_quota_update_message_spec.rb
@@ -20,6 +20,7 @@ module VCAP::CloudController
per_process_memory_in_mb: 1024,
total_instances: 2,
per_app_tasks: 4,
+ log_rate_limit_in_bytes_per_second: 2000,
}
end
@@ -49,6 +50,7 @@ module VCAP::CloudController
expect(subject.per_process_memory_in_mb).to eq(1024)
expect(subject.total_instances).to eq(2)
expect(subject.per_app_tasks).to eq(4)
+ expect(subject.log_rate_limit_in_bytes_per_second).to eq(2000)
expect(subject.paid_services_allowed).to be_truthy
expect(subject.total_service_instances).to eq(17)
expect(subject.total_service_keys).to eq(19)
diff --git a/spec/unit/messages/task_create_message_spec.rb b/spec/unit/messages/task_create_message_spec.rb
index 45635e04e9b..3d7730d348d 100644
--- a/spec/unit/messages/task_create_message_spec.rb
+++ b/spec/unit/messages/task_create_message_spec.rb
@@ -141,6 +141,68 @@ module VCAP::CloudController
end
end
+ describe 'log_rate_limit_in_bytes_per_second' do
+ it 'can be nil' do
+ body.delete 'log_rate_limit_in_bytes_per_second'
+
+ message = TaskCreateMessage.new(body)
+
+ expect(message).to be_valid
+ end
+
+ it 'must be numerical' do
+ body['log_rate_limit_in_bytes_per_second'] = 'trout'
+
+ message = TaskCreateMessage.new(body)
+
+ expect(message).to_not be_valid
+ expect(message.errors.full_messages).to include('Log rate limit in bytes per second is not a number')
+ end
+
+ it 'may not have a floating point' do
+ body['log_rate_limit_in_bytes_per_second'] = 4.5
+
+ message = TaskCreateMessage.new(body)
+
+ expect(message).to_not be_valid
+ expect(message.errors.full_messages).to include('Log rate limit in bytes per second must be an integer')
+ end
+
+ it 'may be -1' do
+ body['log_rate_limit_in_bytes_per_second'] = -1
+
+ message = TaskCreateMessage.new(body)
+
+ expect(message).to be_valid
+ end
+
+ it 'may be zero' do
+ body['log_rate_limit_in_bytes_per_second'] = 0
+
+ message = TaskCreateMessage.new(body)
+
+ expect(message).to be_valid
+ end
+
+ it 'may not be smaller than -1' do
+ body['log_rate_limit_in_bytes_per_second'] = -2
+
+ message = TaskCreateMessage.new(body)
+
+ expect(message).to_not be_valid
+ expect(message.errors.full_messages).to include('Log rate limit in bytes per second must be greater than -2')
+ end
+
+ it 'may not be too large' do
+ body['log_rate_limit_in_bytes_per_second'] = 2**63
+
+ message = TaskCreateMessage.new(body)
+
+ expect(message).to_not be_valid
+ expect(message.errors.full_messages).to include('Log rate limit in bytes per second must be less than or equal to 9223372036854775807')
+ end
+ end
+
describe 'template' do
it 'can be nil' do
body.delete 'template'
diff --git a/spec/unit/models/runtime/constraints/max_log_rate_limit_policy_spec.rb b/spec/unit/models/runtime/constraints/max_log_rate_limit_policy_spec.rb
new file mode 100644
index 00000000000..a9c3c075e9e
--- /dev/null
+++ b/spec/unit/models/runtime/constraints/max_log_rate_limit_policy_spec.rb
@@ -0,0 +1,162 @@
+require 'spec_helper'
+
+RSpec.describe 'max log_rate_limit policies' do
+ let(:org_or_space) { double(:org_or_space, has_remaining_log_rate_limit: false) }
+ let(:error_name) { :random_log_rate_limit_error }
+
+ describe AppMaxLogRateLimitPolicy do
+ subject(:validator) { AppMaxLogRateLimitPolicy.new(process, org_or_space, error_name) }
+
+ context 'when the app specifies a log quota' do
+ let(:process) { VCAP::CloudController::ProcessModelFactory.make(log_rate_limit: 100, state: 'STOPPED') }
+
+ context 'when the app is being started' do
+ before do
+ process.state = 'STARTED'
+ process.log_rate_limit = 150
+ end
+
+ it 'registers an error when quota is exceeded' do
+ expect(org_or_space).to receive(:has_remaining_log_rate_limit).with(150).and_return(false)
+ expect(validator).to validate_with_error(process, :log_rate_limit, error_name)
+ end
+
+ it 'does not register an error when quota is not exceeded' do
+ expect(org_or_space).to receive(:has_remaining_log_rate_limit).with(150).and_return(true)
+ expect(validator).to validate_without_error(process)
+ end
+
+ it 'adds the given error to the model' do
+ expect(org_or_space).to receive(:has_remaining_log_rate_limit).with(150).and_return(false)
+ validator.validate
+ expect(process.errors.on(:log_rate_limit)).to include(error_name)
+ end
+ end
+
+ context 'when the app is already started' do
+ let(:process) { VCAP::CloudController::ProcessModelFactory.make(log_rate_limit: 100, state: 'STARTED') }
+
+ it 'does not register an error when quota is exceeded' do
+ expect(validator).to validate_without_error(process)
+ end
+
+ context 'when the instance count has changed and would exceed the quota' do
+ before do
+ process.instances = 5
+ end
+ it 'registers an error' do
+ expect(org_or_space).to receive(:has_remaining_log_rate_limit).with(400).and_return(false)
+ expect(validator).to validate_with_error(process, :log_rate_limit, error_name)
+ end
+ end
+ end
+
+ context 'when the app is being stopped' do
+ before do
+ process.state = 'STOPPED'
+ end
+
+ it 'does not register an error' do
+ expect(validator).to validate_without_error(process)
+ end
+
+ context 'when the instance count has changed and would exceed the quota' do
+ before do
+ process.instances = 5
+ end
+ it 'does not register an error' do
+ expect(validator).to validate_without_error(process)
+ end
+ end
+ end
+ end
+
+ context 'when the app does not specify a log quota' do
+ let(:process) { VCAP::CloudController::ProcessModelFactory.make(log_rate_limit: -1, state: 'STOPPED') }
+ before do
+ process.state = 'STARTED'
+ end
+
+ context 'when the org specifies a log quota' do
+ before do
+ allow(org_or_space).to receive(:name).and_return('some-org')
+ allow(org_or_space).to receive(:log_rate_limit).and_return(5000)
+ end
+
+ it 'is unhappy and adds an error' do
+ validator.validate
+ expect(process.errors.on(:log_rate_limit)).to include("cannot be unlimited in organization 'some-org'.")
+ end
+ end
+
+ context 'when the space specifies a log quota' do
+ before do
+ allow(org_or_space).to receive(:name).and_return('some-space')
+ allow(org_or_space).to receive(:log_rate_limit).and_return(5000)
+ allow(org_or_space).to receive(:organization_guid).and_return('some-org-guid')
+ end
+
+ it 'is unhappy and adds an error' do
+ validator.validate
+ expect(process.errors.on(:log_rate_limit)).to include("cannot be unlimited in space 'some-space'.")
+ end
+ end
+
+ context 'when both the space and the org specify an infinite log quota' do
+ before do
+ expect(org_or_space).to receive(:log_rate_limit).and_return(VCAP::CloudController::QuotaDefinition::UNLIMITED)
+ expect(org_or_space).to receive(:has_remaining_log_rate_limit).and_return(true)
+ end
+
+ it 'is happy' do
+ expect(validator).to validate_without_error(process)
+ end
+ end
+ end
+ end
+
+ describe TaskMaxLogRateLimitPolicy do
+ subject(:validator) { TaskMaxLogRateLimitPolicy.new(task, org_or_space, error_name) }
+
+ let(:task) { VCAP::CloudController::TaskModel.make log_rate_limit: 150, state: VCAP::CloudController::TaskModel::RUNNING_STATE }
+
+ context 'when not cancelling a task' do
+ it 'registers error when quota is exceeded' do
+ allow(org_or_space).to receive(:has_remaining_log_rate_limit).with(150).and_return(false)
+ expect(validator).to validate_with_error(task, :log_rate_limit, error_name)
+ end
+
+ it 'does not register error when quota is not exceeded' do
+ allow(org_or_space).to receive(:has_remaining_log_rate_limit).with(150).and_return(true)
+ expect(validator).to validate_without_error(task)
+ end
+
+ it 'adds the given error to the model' do
+ allow(org_or_space).to receive(:has_remaining_log_rate_limit).with(150).and_return(false)
+ validator.validate
+ expect(task.errors.on(:log_rate_limit)).to include(error_name)
+ end
+ end
+
+ context 'when canceling a task' do
+ it 'does not register an error' do
+ task.state = VCAP::CloudController::TaskModel::CANCELING_STATE
+ expect(validator).to validate_without_error(task)
+ end
+ end
+
+ context 'when the task is SUCCEEDED' do
+ it 'does not register error' do
+ task.state = VCAP::CloudController::TaskModel::SUCCEEDED_STATE
+ expect(validator).to validate_without_error(task)
+ end
+ end
+
+ context 'when the task is FAILED' do
+ it 'does not register error' do
+ task.state = VCAP::CloudController::TaskModel::FAILED_STATE
+ expect(validator).to validate_without_error(task)
+ end
+ end
+ end
+end
diff --git a/spec/unit/models/runtime/constraints/min_log_rate_limit_policy_spec.rb b/spec/unit/models/runtime/constraints/min_log_rate_limit_policy_spec.rb
new file mode 100644
index 00000000000..c106da7283d
--- /dev/null
+++ b/spec/unit/models/runtime/constraints/min_log_rate_limit_policy_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+RSpec.describe MinLogRateLimitPolicy do
+ let(:process) { VCAP::CloudController::ProcessModelFactory.make }
+
+ subject(:validator) { MinLogRateLimitPolicy.new(process) }
+
+ it 'when requested size is negative 2' do
+ allow(process).to receive(:log_rate_limit).and_return(-2)
+ expect(validator).to validate_with_error(process, :log_rate_limit, MinLogRateLimitPolicy::ERROR_MSG)
+ end
+
+ it 'when requested size is negative 1' do
+ allow(process).to receive(:log_rate_limit).and_return(-1)
+ expect(validator).to validate_without_error(process)
+ end
+
+ it 'when requested size is zero' do
+ allow(process).to receive(:log_rate_limit).and_return(0)
+ expect(validator).to validate_without_error(process)
+ end
+
+ it 'when requested size is positive' do
+ allow(process).to receive(:log_rate_limit).and_return(1)
+ expect(validator).to validate_without_error(process)
+ end
+end
diff --git a/spec/unit/models/runtime/organization_spec.rb b/spec/unit/models/runtime/organization_spec.rb
index 8ff2b5b450a..84ef6da4a84 100644
--- a/spec/unit/models/runtime/organization_spec.rb
+++ b/spec/unit/models/runtime/organization_spec.rb
@@ -508,6 +508,59 @@ module VCAP::CloudController
end
end
+ describe '#has_remaining_log_rate_limit' do
+ let(:log_rate_limit) { 10 }
+ let(:quota) { QuotaDefinition.make(log_rate_limit: log_rate_limit) }
+ let(:org) { Organization.make(quota_definition: quota) }
+ let(:org2) { Organization.make(quota_definition: quota) }
+ let(:space) { Space.make(organization: org) }
+ let(:space2) { Space.make(organization: org) }
+ let(:space_org2) { Space.make(organization: org2) }
+ let!(:app_model) { AppModel.make(space: space2) }
+
+ context 'when the quota is unlimited' do
+ let(:log_rate_limit) { QuotaDefinition::UNLIMITED }
+
+ it 'handles large log quotas' do
+ expect(org.has_remaining_log_rate_limit(10_000_000)).to be_truthy
+ end
+ end
+
+ context 'when nothing is running' do
+ it 'uses the log_rate_limit' do
+ expect(org.has_remaining_log_rate_limit(10)).to be_truthy
+ expect(org.has_remaining_log_rate_limit(11)).to be_falsey
+ end
+ end
+
+ context 'when something else is running' do
+ it 'takes all things in the org into account' do
+ ProcessModelFactory.make(space: space, log_rate_limit: 5, state: 'STARTED')
+ expect(org.has_remaining_log_rate_limit(5)).to be_truthy
+ expect(org.has_remaining_log_rate_limit(6)).to be_falsey
+
+ ProcessModelFactory.make(space: space, log_rate_limit: 1, state: 'STARTED')
+ expect(org.has_remaining_log_rate_limit(4)).to be_truthy
+ expect(org.has_remaining_log_rate_limit(5)).to be_falsey
+
+ TaskModel.make(app: app_model, log_rate_limit: 1, state: TaskModel::RUNNING_STATE)
+ expect(org.has_remaining_log_rate_limit(3)).to be_truthy
+ expect(org.has_remaining_log_rate_limit(4)).to be_falsey
+ end
+
+ context 'when something else is running in another org' do
+ it 'only accounts for things running in the owning org' do
+ ProcessModelFactory.make(space: space_org2, log_rate_limit: 1, instances: 2, state: 'STARTED')
+ expect(org.has_remaining_log_rate_limit(10)).to be_truthy
+ expect(org.has_remaining_log_rate_limit(11)).to be_falsey
+
+ expect(org2.has_remaining_log_rate_limit(8)).to be_truthy
+ expect(org2.has_remaining_log_rate_limit(9)).to be_falsey
+ end
+ end
+ end
+ end
+
describe '#instance_memory_limit' do
let(:quota) { QuotaDefinition.make(instance_memory_limit: 50) }
let(:org) { Organization.make quota_definition: quota }
diff --git a/spec/unit/models/runtime/process_model_spec.rb b/spec/unit/models/runtime/process_model_spec.rb
index 7049f84707c..e571187cd86 100644
--- a/spec/unit/models/runtime/process_model_spec.rb
+++ b/spec/unit/models/runtime/process_model_spec.rb
@@ -74,6 +74,11 @@ def expect_no_validator(validator_class)
expect(process.ports).to eq([8081, 8082])
end
end
+
+ it 'has a default log_rate_limit' do
+ TestConfig.override(default_app_log_rate_limit_in_bytes_per_second: 873565)
+ expect(process.log_rate_limit).to eq(873565)
+ end
end
describe 'Associations' do
@@ -193,6 +198,7 @@ def expect_no_validator(validator_class)
expect_validator(InstancesPolicy)
expect_validator(MaxDiskQuotaPolicy)
expect_validator(MinDiskQuotaPolicy)
+ expect_validator(MinLogRateLimitPolicy)
expect_validator(MinMemoryPolicy)
expect_validator(AppMaxInstanceMemoryPolicy)
expect_validator(InstancesPolicy)
@@ -289,6 +295,15 @@ def expect_no_validator(validator_class)
end
end
+ describe 'log_rate_limit' do
+ subject(:process) { ProcessModelFactory.make }
+
+ it 'does not allow a log_rate_limit below the minimum' do
+ process.log_rate_limit = -2
+ expect(process).to_not be_valid
+ end
+ end
+
describe 'health_check_http_endpoint' do
subject(:process) { ProcessModelFactory.make }
@@ -402,11 +417,12 @@ def expect_no_validator(validator_class)
describe 'quota' do
subject(:process) { ProcessModelFactory.make }
+ let(:log_rate_limit) { 1024 }
let(:quota) do
- QuotaDefinition.make(memory_limit: 128)
+ QuotaDefinition.make(memory_limit: 128, log_rate_limit: log_rate_limit)
end
let(:space_quota) do
- SpaceQuotaDefinition.make(memory_limit: 128, organization: org)
+ SpaceQuotaDefinition.make(memory_limit: 128, organization: org, log_rate_limit: log_rate_limit)
end
context 'app update' do
@@ -420,15 +436,52 @@ def act_as_cf_admin
let(:org) { Organization.make(quota_definition: quota) }
let(:space) { Space.make(name: 'hi', organization: org, space_quota_definition: space_quota) }
let(:parent_app) { AppModel.make(space: space) }
- subject!(:process) { ProcessModelFactory.make(app: parent_app, memory: 64, instances: 2, state: 'STARTED') }
+ subject!(:process) { ProcessModelFactory.make(app: parent_app, memory: 64, log_rate_limit: 512, instances: 2, state: 'STOPPED') }
it 'should raise error when quota is exceeded' do
process.memory = 65
- expect { process.save }.to raise_error(/quota_exceeded/)
+ process.state = 'STARTED'
+ expect { process.save }.to raise_error(/memory quota_exceeded/)
+ end
+
+ it 'should raise error when log quota is exceeded' do
+ number = (log_rate_limit / 2) + 1
+ process.log_rate_limit = number
+ process.state = 'STARTED'
+ expect { process.save }.to raise_error(/exceeds space log rate quota/)
+ end
+
+ context 'when only exceeding the org quota' do
+ before do
+ org.quota_definition = QuotaDefinition.make(log_rate_limit: 5)
+ org.save
+ end
+
+ it 'raises an error' do
+ process.log_rate_limit = 10
+ process.state = 'STARTED'
+ expect { process.save }.to raise_error(/exceeds organization log rate quota/)
+ end
+ end
+
+ it 'should not raise error when log quota is not exceeded' do
+ number = (log_rate_limit / 2)
+ process.log_rate_limit = number
+ process.state = 'STARTED'
+ expect { process.save }.not_to raise_error
+ end
+
+ it 'should raise an error when starting an app with unlimited log rate and a limited quota' do
+ process.log_rate_limit = -1
+ process.state = 'STARTED'
+ expect { process.save }.to raise_error(Sequel::ValidationFailed)
+ expect(process.errors.on(:log_rate_limit)).to include("cannot be unlimited in organization '#{org.name}'.")
+ expect(process.errors.on(:log_rate_limit)).to include("cannot be unlimited in space '#{space.name}'.")
end
it 'should not raise error when quota is not exceeded' do
process.memory = 63
+ process.state = 'STARTED'
expect { process.save }.to_not raise_error
end
@@ -436,12 +489,15 @@ def act_as_cf_admin
quota.memory_limit = 32
quota.save
process.memory = 100
+ process.state = 'STARTED'
process.save(validate: false)
expect(process.reload).to_not be_valid
expect { process.delete }.not_to raise_error
end
it 'allows scaling down instances of an app from above quota to below quota' do
+ process.update(state: 'STARTED')
+
org.quota_definition = QuotaDefinition.make(memory_limit: 72)
act_as_cf_admin { org.save }
@@ -460,6 +516,7 @@ def act_as_cf_admin
quota.save
process.instances = 5
+ process.state = 'STARTED'
expect { process.save }.to raise_error(/instance_limit_exceeded/)
end
@@ -471,10 +528,13 @@ def act_as_cf_admin
quota.save
process.instances = 5
+ process.state = 'STARTED'
expect { process.save }.to raise_error(/instance_limit_exceeded/)
end
it 'raises when scaling down number of instances but remaining above quota' do
+ process.update(state: 'STARTED')
+
org.quota_definition = QuotaDefinition.make(memory_limit: 32)
act_as_cf_admin { org.save }
@@ -487,6 +547,7 @@ def act_as_cf_admin
end
it 'allows stopping an app that is above quota' do
+ process.update(state: 'STARTED')
org.quota_definition = QuotaDefinition.make(memory_limit: 72)
act_as_cf_admin { org.save }
@@ -503,6 +564,7 @@ def act_as_cf_admin
act_as_cf_admin { org.save }
process.memory = 40
+ process.state = 'STARTED'
expect { process.save }.to raise_error(Sequel::ValidationFailed, /quota_exceeded/)
process.memory = 32
@@ -565,6 +627,7 @@ def act_as_cf_admin
:health_check_timeout,
:health_check_type,
:instances,
+ :log_rate_limit,
:memory,
:name,
:package_state,
@@ -598,6 +661,7 @@ def act_as_cf_admin
:health_check_timeout,
:health_check_type,
:instances,
+ :log_rate_limit,
:memory,
:name,
:production,
@@ -1333,6 +1397,22 @@ def act_as_cf_admin
end
end
+ describe 'default log_rate_limit' do
+ before do
+ TestConfig.override(default_app_log_rate_limit_in_bytes_per_second: 1024)
+ end
+
+ it 'should use the provided quota' do
+ process = ProcessModel.make(log_rate_limit: 256)
+ expect(process.log_rate_limit).to eq(256)
+ end
+
+ it 'should use the default quota' do
+ process = ProcessModel.make
+ expect(process.log_rate_limit).to eq(1024)
+ end
+ end
+
describe 'instance_file_descriptor_limit' do
before do
TestConfig.override(instance_file_descriptor_limit: 200)
diff --git a/spec/unit/models/runtime/quota_definition_spec.rb b/spec/unit/models/runtime/quota_definition_spec.rb
index a886605b92d..7c690a4d27d 100644
--- a/spec/unit/models/runtime/quota_definition_spec.rb
+++ b/spec/unit/models/runtime/quota_definition_spec.rb
@@ -110,6 +110,15 @@ module VCAP::CloudController
expect(quota_definition).to be_valid
end
+ it 'log_rate_limit cannot be less than -1 (unlimited)' do
+ quota_definition.log_rate_limit = -2
+ expect(quota_definition).not_to be_valid
+ expect(quota_definition.errors.on(:log_rate_limit)).to include(:invalid_log_rate_limit)
+
+ quota_definition.log_rate_limit = -1
+ expect(quota_definition).to be_valid
+ end
+
it 'app_task_limit cannot be less than -1 ("unlimited")' do
quota_definition.app_task_limit = -2
expect(quota_definition).not_to be_valid
@@ -133,12 +142,14 @@ module VCAP::CloudController
it {
is_expected.to export_attributes :name, :non_basic_services_allowed, :total_services, :total_routes,
:total_private_domains, :memory_limit, :trial_db_allowed, :instance_memory_limit,
- :app_instance_limit, :app_task_limit, :total_service_keys, :total_reserved_route_ports
+ :app_instance_limit, :app_task_limit, :total_service_keys, :total_reserved_route_ports,
+ :log_rate_limit
}
it {
is_expected.to import_attributes :name, :non_basic_services_allowed, :total_services, :total_routes,
:total_private_domains, :memory_limit, :trial_db_allowed, :instance_memory_limit,
- :app_instance_limit, :app_task_limit, :total_service_keys, :total_reserved_route_ports
+ :app_instance_limit, :app_task_limit, :total_service_keys, :total_reserved_route_ports,
+ :log_rate_limit
}
end
diff --git a/spec/unit/models/runtime/space_quota_definition_spec.rb b/spec/unit/models/runtime/space_quota_definition_spec.rb
index c5438e62fe2..73d986b5632 100644
--- a/spec/unit/models/runtime/space_quota_definition_spec.rb
+++ b/spec/unit/models/runtime/space_quota_definition_spec.rb
@@ -68,6 +68,17 @@ module VCAP::CloudController
end
end
+ describe 'log_rate_limit' do
+ it 'cannot be less than -1' do
+ space_quota_definition.log_rate_limit = -2
+ expect(space_quota_definition).not_to be_valid
+ expect(space_quota_definition.errors.on(:log_rate_limit)).to include(:invalid_log_rate_limit)
+
+ space_quota_definition.log_rate_limit = -1
+ expect(space_quota_definition).to be_valid
+ end
+ end
+
describe 'total_reserved_route_ports' do
let(:err_msg) do
'Total reserved ports must be -1, 0, or a positive integer, must ' \
@@ -163,13 +174,13 @@ module VCAP::CloudController
it do
is_expected.to export_attributes :name, :organization_guid, :non_basic_services_allowed, :total_services,
:total_routes, :memory_limit, :instance_memory_limit, :app_instance_limit, :app_task_limit,
- :total_service_keys, :total_reserved_route_ports
+ :total_service_keys, :total_reserved_route_ports, :log_rate_limit
end
it do
is_expected.to import_attributes :name, :organization_guid, :non_basic_services_allowed, :total_services,
:total_routes, :memory_limit, :instance_memory_limit, :app_instance_limit, :app_task_limit,
- :total_service_keys, :total_reserved_route_ports
+ :total_service_keys, :total_reserved_route_ports, :log_rate_limit
end
end
diff --git a/spec/unit/models/runtime/space_spec.rb b/spec/unit/models/runtime/space_spec.rb
index 2a0812dd497..ebb4f77f504 100644
--- a/spec/unit/models/runtime/space_spec.rb
+++ b/spec/unit/models/runtime/space_spec.rb
@@ -665,6 +665,57 @@ module VCAP::CloudController
end
end
+ describe '#has_remaining_log_rate_limit' do
+ let(:log_rate_limit) { 10 }
+ let(:quota) { SpaceQuotaDefinition.make(log_rate_limit: log_rate_limit, organization: org) }
+ let(:org) { Organization.make }
+ let(:space) { Space.make(organization: org, space_quota_definition: quota) }
+ let(:space2) { Space.make(organization: org, space_quota_definition: quota) }
+ let!(:app_model) { AppModel.make(space: space) }
+
+ context 'when the quota is unlimited' do
+ let(:log_rate_limit) { QuotaDefinition::UNLIMITED }
+
+ it 'handles large log quotas' do
+ expect(space.has_remaining_log_rate_limit(10_000_000)).to be_truthy
+ end
+ end
+
+ context 'when nothing is running' do
+ it 'uses the log_rate_limit' do
+ expect(space.has_remaining_log_rate_limit(10)).to be_truthy
+ expect(space.has_remaining_log_rate_limit(11)).to be_falsey
+ end
+ end
+
+ context 'when something else is running' do
+ it 'takes all things in the space into account' do
+ ProcessModelFactory.make(space: space, log_rate_limit: 5, state: 'STARTED')
+ expect(space.has_remaining_log_rate_limit(5)).to be_truthy
+ expect(space.has_remaining_log_rate_limit(6)).to be_falsey
+
+ ProcessModelFactory.make(space: space, log_rate_limit: 1, state: 'STARTED')
+ expect(space.has_remaining_log_rate_limit(4)).to be_truthy
+ expect(space.has_remaining_log_rate_limit(5)).to be_falsey
+
+ TaskModel.make(app: app_model, log_rate_limit: 1, state: TaskModel::RUNNING_STATE)
+ expect(space.has_remaining_log_rate_limit(3)).to be_truthy
+ expect(space.has_remaining_log_rate_limit(4)).to be_falsey
+ end
+
+ context 'when processes are running in another space' do
+ it 'only accounts for processes running in the owning space' do
+ ProcessModelFactory.make(space: space2, log_rate_limit: 1, instances: 2, state: 'STARTED')
+
+ expect(space.has_remaining_log_rate_limit(10)).to be_truthy
+ expect(space.has_remaining_log_rate_limit(11)).to be_falsey
+ expect(space2.has_remaining_log_rate_limit(8)).to be_truthy
+ expect(space2.has_remaining_log_rate_limit(9)).to be_falsey
+ end
+ end
+ end
+ end
+
describe '#instance_memory_limit' do
let(:org) { Organization.make }
let(:space_quota) { SpaceQuotaDefinition.make(instance_memory_limit: 50, organization: org) }
diff --git a/spec/unit/models/runtime/task_model_spec.rb b/spec/unit/models/runtime/task_model_spec.rb
index c9482fda8bd..88934bd206e 100644
--- a/spec/unit/models/runtime/task_model_spec.rb
+++ b/spec/unit/models/runtime/task_model_spec.rb
@@ -289,6 +289,15 @@ module VCAP::CloudController
end
describe 'quotas' do
+ it 'errors when log_rate_limit is below -1' do
+ expect {
+ TaskModel.make(
+ log_rate_limit: -2,
+ app: app,
+ )
+ }.to raise_error(Sequel::ValidationFailed, /log_rate_limit must be greater than or equal to -1/)
+ end
+
describe 'space quotas' do
let(:space) { Space.make organization: org, space_quota_definition: quota }
@@ -305,6 +314,59 @@ module VCAP::CloudController
end
end
+ describe 'when the log rate limit quota is unlimited' do
+ let(:quota) { SpaceQuotaDefinition.make(log_rate_limit: -1, organization: org) }
+
+ it 'allows tasks to run with unlimited rate limits' do
+ expect {
+ TaskModel.make(
+ log_rate_limit: -1,
+ app: app,
+ )
+ }.not_to raise_error
+ end
+
+ it 'allows tasks to run with rate limits' do
+ expect {
+ TaskModel.make(
+ log_rate_limit: 1_000_000_000_000,
+ app: app,
+ )
+ }.not_to raise_error
+ end
+ end
+
+ describe 'when the quota has a log_rate_limit' do
+ let(:quota) { SpaceQuotaDefinition.make(log_rate_limit: 200, organization: org) }
+
+ it 'allows tasks that fit in the available log rate' do
+ expect {
+ TaskModel.make(
+ log_rate_limit: 100,
+ app: app,
+ )
+ }.not_to raise_error
+ end
+
+ it 'raises an error if the task does not fit in the remaining space' do
+ expect {
+ TaskModel.make(
+ log_rate_limit: 201,
+ app: app,
+ )
+ }.to raise_error Sequel::ValidationFailed, 'log_rate_limit exceeds space log rate quota'
+ end
+
+ it 'raises an error if the task has an unlimited rate limit' do
+ expect {
+ TaskModel.make(
+ log_rate_limit: -1,
+ app: app,
+ )
+ }.to raise_error Sequel::ValidationFailed, "log_rate_limit cannot be unlimited in space '#{space.name}'."
+ end
+ end
+
describe 'when the quota has a memory_limit' do
let(:quota) { SpaceQuotaDefinition.make(memory_limit: 20, organization: org) }
@@ -407,12 +469,35 @@ module VCAP::CloudController
expect {
TaskModel.make(
memory_in_mb: 21,
+ log_rate_limit: 21_000,
app: app,
)
}.not_to raise_error
end
end
+ describe 'when the quota has a log_rate_limit' do
+ let(:quota) { QuotaDefinition.make(log_rate_limit: 200) }
+
+ it 'does allow a task that fits in the limit to start' do
+ expect {
+ TaskModel.make(
+ log_rate_limit: 199,
+ app: app,
+ )
+ }.to_not raise_error
+ end
+
+ it 'does not allow a task that exceeds the limit to start' do
+ expect {
+ TaskModel.make(
+ log_rate_limit: 10_000,
+ app: app,
+ )
+ }.to raise_error /log_rate_limit exceeds organization log rate/
+ end
+ end
+
describe 'when the quota has a memory_limit' do
let(:quota) { QuotaDefinition.make(memory_limit: 20) }
diff --git a/spec/unit/presenters/v2/process_model_presenter_spec.rb b/spec/unit/presenters/v2/process_model_presenter_spec.rb
index d1c03c13256..9ff7ebcbb8b 100644
--- a/spec/unit/presenters/v2/process_model_presenter_spec.rb
+++ b/spec/unit/presenters/v2/process_model_presenter_spec.rb
@@ -69,6 +69,7 @@ module CloudController::Presenters::V2
'memory' => 1024,
'instances' => 1,
'disk_quota' => 1024,
+ 'log_rate_limit' => 1_048_576,
'state' => 'STOPPED',
'version' => process.version,
'command' => 'start',
diff --git a/spec/unit/presenters/v3/app_manifest_presenter_spec.rb b/spec/unit/presenters/v3/app_manifest_presenter_spec.rb
index e67b1d71e00..31df7131d89 100644
--- a/spec/unit/presenters/v3/app_manifest_presenter_spec.rb
+++ b/spec/unit/presenters/v3/app_manifest_presenter_spec.rb
@@ -78,6 +78,7 @@ module VCAP::CloudController::Presenters::V3
health_check_type: 'http',
health_check_http_endpoint: '/foobar',
health_check_timeout: 5,
+ log_rate_limit: 1_048_576,
command: 'Do it now!',
type: 'aaaaa',
)
@@ -119,6 +120,7 @@ module VCAP::CloudController::Presenters::V3
'type' => process1.type,
'instances' => process1.instances,
'memory' => "#{process1.memory}M",
+ 'log-rate-limit-per-second' => '1M',
'disk_quota' => "#{process1.disk_quota}M",
'command' => process1.command,
'health-check-type' => process1.health_check_type,
@@ -128,6 +130,7 @@ module VCAP::CloudController::Presenters::V3
{
'type' => process2.type,
'instances' => process2.instances,
+ 'log-rate-limit-per-second' => '1M',
'memory' => "#{process2.memory}M",
'disk_quota' => "#{process2.disk_quota}M",
'health-check-type' => process2.health_check_type,
@@ -166,6 +169,7 @@ module VCAP::CloudController::Presenters::V3
{
'type' => process1.type,
'instances' => process1.instances,
+ 'log-rate-limit-per-second' => '1M',
'memory' => "#{process1.memory}M",
'disk_quota' => "#{process1.disk_quota}M",
'health-check-type' => process1.health_check_type,
@@ -173,6 +177,7 @@ module VCAP::CloudController::Presenters::V3
{
'type' => process2.type,
'instances' => process2.instances,
+ 'log-rate-limit-per-second' => '1M',
'memory' => "#{process2.memory}M",
'disk_quota' => "#{process2.disk_quota}M",
'health-check-type' => process2.health_check_type,
diff --git a/spec/unit/presenters/v3/app_manifest_presenters/process_properties_presenter_spec.rb b/spec/unit/presenters/v3/app_manifest_presenters/process_properties_presenter_spec.rb
index e4684dd1fe6..5945871e56a 100644
--- a/spec/unit/presenters/v3/app_manifest_presenters/process_properties_presenter_spec.rb
+++ b/spec/unit/presenters/v3/app_manifest_presenters/process_properties_presenter_spec.rb
@@ -32,5 +32,31 @@ module VCAP::CloudController::Presenters::V3::AppManifestPresenters
end
end
end
+
+ describe '#process_hash' do
+ let(:process) { VCAP::CloudController::ProcessModel.make }
+
+ it 'renders a compact hash of the process' do
+ hash = subject.process_hash(process)
+ expect(hash).to eq({
+ 'type' => 'web',
+ 'instances' => 1,
+ 'memory' => '1024M',
+ 'disk_quota' => '1024M',
+ 'log-rate-limit-per-second' => '1M',
+ 'health-check-type' => 'port',
+ })
+ end
+ end
+
+ describe '#add_units_log_rate_limit' do
+ it 'is consistent with other quotas with output' do
+ expect(subject.add_units_log_rate_limit(-1)).to eq(-1)
+ expect(subject.add_units_log_rate_limit(256)).to eq('256B')
+ expect(subject.add_units_log_rate_limit(2_048)).to eq('2K')
+ expect(subject.add_units_log_rate_limit(4_194_304)).to eq('4M')
+ expect(subject.add_units_log_rate_limit(6_442_450_944)).to eq('6G')
+ end
+ end
end
end
diff --git a/spec/unit/presenters/v3/build_presenter_spec.rb b/spec/unit/presenters/v3/build_presenter_spec.rb
index 11f21d900cd..3c66ef5e2d7 100644
--- a/spec/unit/presenters/v3/build_presenter_spec.rb
+++ b/spec/unit/presenters/v3/build_presenter_spec.rb
@@ -15,6 +15,7 @@ module VCAP::CloudController::Presenters::V3
app: app,
staging_memory_in_mb: 1024,
staging_disk_in_mb: 1024,
+ staging_log_rate_limit: 2048,
created_by_user_guid: 'happy user guid',
created_by_user_name: 'happier user name',
created_by_user_email: 'this user emailed in'
@@ -46,6 +47,7 @@ module VCAP::CloudController::Presenters::V3
expect(result[:staging_memory_in_mb]).to eq(1024)
expect(result[:staging_disk_in_mb]).to eq(1024)
+ expect(result[:staging_log_rate_limit_bytes_per_second]).to eq(2048)
expect(result[:created_at]).to be_a(Time)
expect(result[:updated_at]).to be_a(Time)
diff --git a/spec/unit/presenters/v3/process_presenter_spec.rb b/spec/unit/presenters/v3/process_presenter_spec.rb
index 09894f8c890..affccd4c66b 100644
--- a/spec/unit/presenters/v3/process_presenter_spec.rb
+++ b/spec/unit/presenters/v3/process_presenter_spec.rb
@@ -147,6 +147,15 @@ module VCAP::CloudController::Presenters::V3
expect(result[:command]).to eq('[PRIVATE DATA HIDDEN]')
end
end
+
+ context 'log quota is -1' do
+ before do
+ process.log_rate_limit = -1
+ end
+ it 'displays it as unlimited' do
+ expect(result[:log_rate_limit_in_bytes_per_second]).to eq(-1)
+ end
+ end
end
end
end
diff --git a/spec/unit/presenters/v3/process_stats_presenter_spec.rb b/spec/unit/presenters/v3/process_stats_presenter_spec.rb
index 53df8e43f12..1599806f5ce 100644
--- a/spec/unit/presenters/v3/process_stats_presenter_spec.rb
+++ b/spec/unit/presenters/v3/process_stats_presenter_spec.rb
@@ -66,12 +66,14 @@ module VCAP::CloudController::Presenters::V3
uptime: 12345,
mem_quota: process[:memory] * 1024 * 1024,
disk_quota: process[:disk_quota] * 1024 * 1024,
+ log_rate_limit: process[:log_rate_limit],
fds_quota: process.file_descriptors,
usage: {
time: '2015-12-08 16:54:48 -0800',
cpu: 80,
mem: 128,
disk: 1024,
+ log_rate: 2048,
}
}
},
@@ -86,12 +88,14 @@ module VCAP::CloudController::Presenters::V3
uptime: 42,
mem_quota: process[:memory] * 1024 * 1024,
disk_quota: process[:disk_quota] * 1024 * 1024,
+ log_rate_limit: process[:log_rate_limit],
fds_quota: process.file_descriptors,
usage: {
time: '2015-03-13 16:54:48 -0800',
cpu: 70,
mem: 128,
disk: 1024,
+ log_rate: 7168,
}
}
},
@@ -116,11 +120,13 @@ module VCAP::CloudController::Presenters::V3
expect(result[0][:uptime]).to eq(12345)
expect(result[0][:mem_quota]).to eq(process[:memory] * 1024 * 1024)
expect(result[0][:disk_quota]).to eq(process[:disk_quota] * 1024 * 1024)
+ expect(result[0][:log_rate_limit]).to eq(process[:log_rate_limit])
expect(result[0][:fds_quota]).to eq(process.file_descriptors)
expect(result[0][:usage]).to eq({ time: '2015-12-08 16:54:48 -0800',
cpu: 80,
mem: 128,
- disk: 1024 })
+ disk: 1024,
+ log_rate: 2048 })
expect(result[1][:type]).to eq(process.type)
expect(result[1][:index]).to eq(1)
@@ -133,7 +139,8 @@ module VCAP::CloudController::Presenters::V3
expect(result[1][:usage]).to eq({ time: '2015-03-13 16:54:48 -0800',
cpu: 70,
mem: 128,
- disk: 1024 })
+ disk: 1024,
+ log_rate: 7168 })
expect(result[2]).to eq(
type: process.type,
@@ -190,11 +197,13 @@ module VCAP::CloudController::Presenters::V3
expect(result[0][:uptime]).to eq(12345)
expect(result[0][:mem_quota]).to eq(process[:memory] * 1024 * 1024)
expect(result[0][:disk_quota]).to eq(process[:disk_quota] * 1024 * 1024)
+ expect(result[0][:log_rate_limit]).to eq(process[:log_rate_limit])
expect(result[0][:fds_quota]).to eq(process.file_descriptors)
expect(result[0][:usage]).to eq({ time: '2015-12-08 16:54:48 -0800',
cpu: 80,
mem: 128,
- disk: 1024 })
+ disk: 1024,
+ log_rate: 2048 })
end
end
@@ -230,6 +239,7 @@ module VCAP::CloudController::Presenters::V3
expect(result[0][:uptime]).to eq(12345)
expect(result[0][:mem_quota]).to be_nil
expect(result[0][:disk_quota]).to be_nil
+ expect(result[0][:log_rate_limit]).to be_nil
expect(result[0][:fds_quota]).to eq(process.file_descriptors)
expect(result[0][:usage]).to eq({})
end
diff --git a/spec/unit/presenters/v3/space_quota_presenter_spec.rb b/spec/unit/presenters/v3/space_quota_presenter_spec.rb
index 20b3cfddac1..2229ff81240 100644
--- a/spec/unit/presenters/v3/space_quota_presenter_spec.rb
+++ b/spec/unit/presenters/v3/space_quota_presenter_spec.rb
@@ -17,6 +17,7 @@ module VCAP::CloudController::Presenters::V3
instance_memory_limit: 3,
app_instance_limit: 4,
app_task_limit: 5,
+ log_rate_limit: 2000,
non_basic_services_allowed: false,
total_services: 6,
total_service_keys: 7,
@@ -42,6 +43,7 @@ module VCAP::CloudController::Presenters::V3
expect(result[:apps][:per_process_memory_in_mb]).to eq(space_quota.instance_memory_limit)
expect(result[:apps][:total_instances]).to eq(space_quota.app_instance_limit)
expect(result[:apps][:per_app_tasks]).to eq(space_quota.app_task_limit)
+ expect(result[:apps][:log_rate_limit_in_bytes_per_second]).to eq(space_quota.log_rate_limit)
expect(result[:services][:paid_services_allowed]).to eq(space_quota.non_basic_services_allowed)
expect(result[:services][:total_service_instances]).to eq(space_quota.total_services)
expect(result[:services][:total_service_keys]).to eq(space_quota.total_service_keys)
@@ -67,6 +69,7 @@ module VCAP::CloudController::Presenters::V3
instance_memory_limit: -1,
app_instance_limit: -1,
app_task_limit: -1,
+ log_rate_limit: -1,
total_services: -1,
total_service_keys: -1,
total_routes: -1,
@@ -79,6 +82,7 @@ module VCAP::CloudController::Presenters::V3
expect(result[:apps][:per_process_memory_in_mb]).to be_nil
expect(result[:apps][:total_instances]).to be_nil
expect(result[:apps][:per_app_tasks]).to be_nil
+ expect(result[:apps][:log_rate_limit_in_bytes_per_second]).to be_nil
expect(result[:services][:total_service_instances]).to be_nil
expect(result[:services][:total_service_keys]).to be_nil
expect(result[:routes][:total_routes]).to be_nil
diff --git a/spec/unit/presenters/v3/task_presenter_spec.rb b/spec/unit/presenters/v3/task_presenter_spec.rb
index 4befd87aa89..a1db39405a7 100644
--- a/spec/unit/presenters/v3/task_presenter_spec.rb
+++ b/spec/unit/presenters/v3/task_presenter_spec.rb
@@ -9,6 +9,7 @@ module VCAP::CloudController::Presenters::V3
failure_reason: 'sup dawg',
memory_in_mb: 2048,
disk_in_mb: 4048,
+ log_rate_limit: 1024,
created_at: Time.at(1),
sequence_id: 5
)
@@ -64,6 +65,7 @@ module VCAP::CloudController::Presenters::V3
expect(result[:result][:failure_reason]).to eq 'sup dawg'
expect(result[:memory_in_mb]).to eq(task.memory_in_mb)
expect(result[:disk_in_mb]).to eq(task.disk_in_mb)
+ expect(result[:log_rate_limit_in_bytes_per_second]).to eq(task.log_rate_limit)
expect(result[:sequence_id]).to eq(5)
expect(result[:created_at]).to eq(task.created_at.iso8601)
expect(result[:updated_at]).to eq(task.updated_at.iso8601)