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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ gem 'newrelic_rpm'
gem 'nokogiri', '>=1.10.5'
gem 'oj'
gem 'openssl', '>= 3.2'
gem 'palm_civet'
gem 'prometheus-client'
gem 'public_suffix'
gem 'puma'
Expand Down
4 changes: 1 addition & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,6 @@ GEM
openssl (4.0.0)
os (1.1.4)
ostruct (0.6.3)
palm_civet (1.1.0)
parallel (1.27.0)
parallel_tests (5.5.0)
parallel
Expand Down Expand Up @@ -660,7 +659,6 @@ DEPENDENCIES
nokogiri (>= 1.10.5)
oj
openssl (>= 3.2)
palm_civet
parallel_tests
pg
prometheus-client
Expand Down Expand Up @@ -705,4 +703,4 @@ DEPENDENCIES
webrick (~> 1.9.2)

BUNDLED WITH
2.4.19
2.6.9
10 changes: 5 additions & 5 deletions lib/cloud_controller/app_manifest/byte_converter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'palm_civet'
require 'cloud_controller/byte_quantity'

module VCAP::CloudController
class ByteConverter
Expand All @@ -10,17 +10,17 @@ def convert_to_mb(human_readable_byte_value)
return nil if human_readable_byte_value.blank?
raise NonNumericError unless human_readable_byte_value.to_s.match?(/\A-?\d+(?:\.\d+)?/)

PalmCivet.to_megabytes(human_readable_byte_value.to_s)
rescue PalmCivet::InvalidByteQuantityError
ByteQuantity.to_megabytes(human_readable_byte_value.to_s)
rescue ByteQuantity::InvalidByteQuantityError
raise InvalidUnitsError
end

def convert_to_b(human_readable_byte_value)
return nil if human_readable_byte_value.blank?
raise NonNumericError unless human_readable_byte_value.to_s.match?(/\A-?\d+(?:\.\d+)?/)

PalmCivet.to_bytes(human_readable_byte_value.to_s)
rescue PalmCivet::InvalidByteQuantityError
ByteQuantity.to_bytes(human_readable_byte_value.to_s)
rescue ByteQuantity::InvalidByteQuantityError
raise InvalidUnitsError
end

Expand Down
92 changes: 92 additions & 0 deletions lib/cloud_controller/byte_quantity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Derived from the palm_civet library
# Copyright (c) 2013 Anand Gaitonde
# Licensed under the MIT License
# https://github.com/goodmustache/palm_civet

module VCAP
module CloudController
module ByteQuantity
BYTE = 1.0
KILOBYTE = 1024 * BYTE
MEGABYTE = 1024 * KILOBYTE
GIGABYTE = 1024 * MEGABYTE
TERABYTE = 1024 * GIGABYTE
BYTESPATTERN = /^(-?\d+(?:\.\d+)?)([KMGT]i?B?|B)$/i

class InvalidByteQuantityError < RuntimeError
def initialize(msg='byte quantity must be a positive integer with a unit of measurement like M, MB, MiB, G, GiB, or GB')
super
end
end

# Returns a human-readable byte string of the form 10M, 12.5K, and so forth.
# The following units are available:
# * T: Terabyte
# * G: Gigabyte
# * M: Megabyte
# * K: Kilobyte
# * B: Byte
# The unit that results in the smallest number greater than or equal to 1 is
# always chosen.
def self.byte_size(bytes)
raise TypeError.new('must be an integer or float') unless bytes.is_a? Numeric

case
when bytes >= TERABYTE
unit = 'T'
value = bytes / TERABYTE
when bytes >= GIGABYTE
unit = 'G'
value = bytes / GIGABYTE
when bytes >= MEGABYTE
unit = 'M'
value = bytes / MEGABYTE
when bytes >= KILOBYTE
unit = 'K'
value = bytes / KILOBYTE
when bytes >= BYTE
unit = 'B'
value = bytes
else
return '0'
end

value = sprintf('%g', sprintf('%.1f', value))
value << unit
end

# Parses a string formatted by bytes_size as bytes. Note binary-prefixed and
# SI prefixed units both mean a base-2 units:
# * KB = K = KiB = 1024
# * MB = M = MiB = 1024 * K
# * GB = G = GiB = 1024 * M
# * TB = T = TiB = 1024 * G
def self.to_bytes(bytes)
matches = BYTESPATTERN.match(bytes.strip)
raise InvalidByteQuantityError if matches.nil?

value = Float(matches[1])

case matches[2][0].capitalize
when 'T'
value *= TERABYTE
when 'G'
value *= GIGABYTE
when 'M'
value *= MEGABYTE
when 'K'
value *= KILOBYTE
end

value.to_i
rescue TypeError
raise InvalidByteQuantityError
end

# Parses a string formatted by byte_size as megabytes.
def self.to_megabytes(bytes)
(to_bytes(bytes) / MEGABYTE).to_i
end
end
end
end
166 changes: 166 additions & 0 deletions spec/unit/lib/cloud_controller/byte_quantity_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Derived from the palm_civet library
# Copyright (c) 2013 Anand Gaitonde
# Licensed under the MIT License
# https://github.com/goodmustache/palm_civet

require 'spec_helper'
require 'cloud_controller/byte_quantity'

module VCAP::CloudController
RSpec.describe ByteQuantity do
describe '#byte_size' do
it 'prints in the largest possible unit' do
expect(ByteQuantity.byte_size(10 * ByteQuantity::TERABYTE)).to eq('10T')
expect(ByteQuantity.byte_size(10.5 * ByteQuantity::TERABYTE)).to eq('10.5T')

expect(ByteQuantity.byte_size(10 * ByteQuantity::GIGABYTE)).to eq('10G')
expect(ByteQuantity.byte_size(10.5 * ByteQuantity::GIGABYTE)).to eq('10.5G')

expect(ByteQuantity.byte_size(100 * ByteQuantity::MEGABYTE)).to eq('100M')
expect(ByteQuantity.byte_size(100.5 * ByteQuantity::MEGABYTE)).to eq('100.5M')

expect(ByteQuantity.byte_size(100 * ByteQuantity::KILOBYTE)).to eq('100K')
expect(ByteQuantity.byte_size(100.5 * ByteQuantity::KILOBYTE)).to eq('100.5K')

expect(ByteQuantity.byte_size(1)).to eq('1B')
end

it "prints '0' for zero bytes" do
expect(ByteQuantity.byte_size(0)).to eq('0')
end

it 'raises a type error on non-number values' do
expect do
ByteQuantity.byte_size('something else')
end.to raise_error(TypeError, 'must be an integer or float')
end
end

describe '#to_bytes' do
it 'parses byte amounts with short units (e.g. M, G)' do
expect(ByteQuantity.to_bytes('5B')).to eq(5)
expect(ByteQuantity.to_bytes('5K')).to eq(5 * ByteQuantity::KILOBYTE)
expect(ByteQuantity.to_bytes('5M')).to eq(5 * ByteQuantity::MEGABYTE)
expect(ByteQuantity.to_bytes('5G')).to eq(5 * ByteQuantity::GIGABYTE)
expect(ByteQuantity.to_bytes('5T')).to eq(5 * ByteQuantity::TERABYTE)
end

it 'parses byte amounts that are float (e.g. 5.3KB)' do
expect(ByteQuantity.to_bytes('13.5KB')).to eq(13_824)
expect(ByteQuantity.to_bytes('4.5KB')).to eq(4608)
expect(ByteQuantity.to_bytes('2.55KB')).to eq(2611)
end

it 'parses byte amounts with long units (e.g MB, GB)' do
expect(ByteQuantity.to_bytes('5MB')).to eq(5 * ByteQuantity::MEGABYTE)
expect(ByteQuantity.to_bytes('5mb')).to eq(5 * ByteQuantity::MEGABYTE)
expect(ByteQuantity.to_bytes('2GB')).to eq(2 * ByteQuantity::GIGABYTE)
expect(ByteQuantity.to_bytes('3TB')).to eq(3 * ByteQuantity::TERABYTE)
end

it 'parses byte amounts with long binary units (e.g MiB, GiB)' do
expect(ByteQuantity.to_bytes('5MiB')).to eq(5 * ByteQuantity::MEGABYTE)
expect(ByteQuantity.to_bytes('5mib')).to eq(5 * ByteQuantity::MEGABYTE)
expect(ByteQuantity.to_bytes('2GiB')).to eq(2 * ByteQuantity::GIGABYTE)
expect(ByteQuantity.to_bytes('3TiB')).to eq(3 * ByteQuantity::TERABYTE)
end

it 'allows whitespace before and after the value' do
expect(ByteQuantity.to_bytes("\t\n\r 5MB ")).to eq(5 * ByteQuantity::MEGABYTE)
end

context 'when the byte amount is 0' do
it 'returns 0 bytes' do
expect(ByteQuantity.to_bytes('0TB')).to eq(0)
end
end

context 'when the byte amount is negative' do
it 'returns a negative amount of bytes' do
expect(ByteQuantity.to_bytes('-200B')).to eq(-200)
end
end

context 'when it raises an error' do
it 'raises when the unit is missing' do
expect do
ByteQuantity.to_bytes('5')
end.to raise_error(ByteQuantity::InvalidByteQuantityError)
end

it 'raises when the unit is unrecognized' do
expect do
ByteQuantity.to_bytes('5MBB')
end.to raise_error(ByteQuantity::InvalidByteQuantityError)

expect do
ByteQuantity.to_bytes('5BB')
end.to raise_error(ByteQuantity::InvalidByteQuantityError)
end
end
end

describe '#to_megabytes' do
it 'parses byte amounts with short units (e.g. M, G)' do
expect(ByteQuantity.to_megabytes('5B')).to eq(0)
expect(ByteQuantity.to_megabytes('5K')).to eq(0)
expect(ByteQuantity.to_megabytes('5M')).to eq(5)
expect(ByteQuantity.to_megabytes('5m')).to eq(5)
expect(ByteQuantity.to_megabytes('5G')).to eq(5120)
expect(ByteQuantity.to_megabytes('5T')).to eq(5_242_880)
end

it 'parses byte amounts with long units (e.g MB, GB)' do
expect(ByteQuantity.to_megabytes('5B')).to eq(0)
expect(ByteQuantity.to_megabytes('5KB')).to eq(0)
expect(ByteQuantity.to_megabytes('5MB')).to eq(5)
expect(ByteQuantity.to_megabytes('5mb')).to eq(5)
expect(ByteQuantity.to_megabytes('5GB')).to eq(5120)
expect(ByteQuantity.to_megabytes('5TB')).to eq(5_242_880)
end

it 'parses byte amounts with long binary units (e.g MiB, GiB)' do
expect(ByteQuantity.to_megabytes('5B')).to eq(0)
expect(ByteQuantity.to_megabytes('5KiB')).to eq(0)
expect(ByteQuantity.to_megabytes('5MiB')).to eq(5)
expect(ByteQuantity.to_megabytes('5mib')).to eq(5)
expect(ByteQuantity.to_megabytes('5GiB')).to eq(5120)
expect(ByteQuantity.to_megabytes('5TiB')).to eq(5_242_880)
end

it 'allows whitespace before and after the value' do
expect(ByteQuantity.to_megabytes("\t\n\r 5MB ")).to eq(5)
end

context 'when the byte amount is 0' do
it 'returns 0 megabytes' do
expect(ByteQuantity.to_megabytes('0TB')).to eq(0)
end
end

context 'when the byte amount is negative' do
it 'returns a negative amount of megabytes' do
expect(ByteQuantity.to_megabytes('-200MB')).to eq(-200)
end
end

context 'when it raises an error' do
it 'raises when the unit is missing' do
expect do
ByteQuantity.to_megabytes('5')
end.to raise_error(ByteQuantity::InvalidByteQuantityError)
end

it 'raises when the unit is unrecognized' do
expect do
ByteQuantity.to_megabytes('5MBB')
end.to raise_error(ByteQuantity::InvalidByteQuantityError)

expect do
ByteQuantity.to_megabytes('5BB')
end.to raise_error(ByteQuantity::InvalidByteQuantityError)
end
end
end
end
end