diff --git a/Gemfile b/Gemfile index e7dffa5f72f..d75051b3db8 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/lib/cloud_controller/app_manifest/byte_converter.rb b/lib/cloud_controller/app_manifest/byte_converter.rb index fc6f8b1be46..338502ce7ff 100644 --- a/lib/cloud_controller/app_manifest/byte_converter.rb +++ b/lib/cloud_controller/app_manifest/byte_converter.rb @@ -1,4 +1,4 @@ -require 'palm_civet' +require 'cloud_controller/palm_civet' module VCAP::CloudController class ByteConverter diff --git a/lib/cloud_controller/palm_civet.rb b/lib/cloud_controller/palm_civet.rb new file mode 100644 index 00000000000..b02d2b92d20 --- /dev/null +++ b/lib/cloud_controller/palm_civet.rb @@ -0,0 +1,91 @@ +module VCAP + module CloudController + module PalmCivet + 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) + if !bytes.is_a? Numeric + raise TypeError, "must be an integer or float" + end + + 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 = "%g" % ("%.1f" % value) + return 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) + if matches == nil + raise InvalidByteQuantityError + end + + value = Float(matches[1]) + + case matches[2][0].capitalize + when "T" + value = value * TERABYTE + when "G" + value = value * GIGABYTE + when "M" + value = value * MEGABYTE + when "K" + value = value * KILOBYTE + end + + return value.to_i + rescue TypeError + raise InvalidByteQuantityError + end + + # Parses a string formatted by byte_size as megabytes. + def self.to_megabytes(bytes) + (self.to_bytes(bytes) / MEGABYTE).to_i + end + end + end +end diff --git a/spec/unit/lib/cloud_controller/palm_civet_spec.rb b/spec/unit/lib/cloud_controller/palm_civet_spec.rb new file mode 100644 index 00000000000..b6934aa9020 --- /dev/null +++ b/spec/unit/lib/cloud_controller/palm_civet_spec.rb @@ -0,0 +1,161 @@ +require 'spec_helper' +require 'cloud_controller/palm_civet' + +module VCAP::CloudController + RSpec.describe PalmCivet do + describe "#byte_size" do + it "prints in the largest possible unit" do + expect(PalmCivet.byte_size(10 * PalmCivet::TERABYTE)).to eq("10T") + expect(PalmCivet.byte_size(10.5 * PalmCivet::TERABYTE)).to eq("10.5T") + + expect(PalmCivet.byte_size(10 * PalmCivet::GIGABYTE)).to eq("10G") + expect(PalmCivet.byte_size(10.5 * PalmCivet::GIGABYTE)).to eq("10.5G") + + expect(PalmCivet.byte_size(100 * PalmCivet::MEGABYTE)).to eq("100M") + expect(PalmCivet.byte_size(100.5 * PalmCivet::MEGABYTE)).to eq("100.5M") + + expect(PalmCivet.byte_size(100 * PalmCivet::KILOBYTE)).to eq("100K") + expect(PalmCivet.byte_size(100.5 * PalmCivet::KILOBYTE)).to eq("100.5K") + + expect(PalmCivet.byte_size(1)).to eq("1B") + end + + it "prints '0' for zero bytes" do + expect(PalmCivet.byte_size(0)).to eq("0") + end + + it "raises a type error on non-number values" do + expect { + PalmCivet.byte_size("something else") + }.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(PalmCivet.to_bytes("5B")).to eq(5) + expect(PalmCivet.to_bytes("5K")).to eq(5 * PalmCivet::KILOBYTE) + expect(PalmCivet.to_bytes("5M")).to eq(5 * PalmCivet::MEGABYTE) + expect(PalmCivet.to_bytes("5G")).to eq(5 * PalmCivet::GIGABYTE) + expect(PalmCivet.to_bytes("5T")).to eq(5 * PalmCivet::TERABYTE) + end + + it "parses byte amounts that are float (e.g. 5.3KB)" do + expect(PalmCivet.to_bytes("13.5KB")).to eq(13824) + expect(PalmCivet.to_bytes("4.5KB")).to eq(4608) + expect(PalmCivet.to_bytes("2.55KB")).to eq(2611) + end + + it "parses byte amounts with long units (e.g MB, GB)" do + expect(PalmCivet.to_bytes("5MB")).to eq(5 * PalmCivet::MEGABYTE) + expect(PalmCivet.to_bytes("5mb")).to eq(5 * PalmCivet::MEGABYTE) + expect(PalmCivet.to_bytes("2GB")).to eq(2 * PalmCivet::GIGABYTE) + expect(PalmCivet.to_bytes("3TB")).to eq(3 * PalmCivet::TERABYTE) + end + + it "parses byte amounts with long binary units (e.g MiB, GiB)" do + expect(PalmCivet.to_bytes("5MiB")).to eq(5 * PalmCivet::MEGABYTE) + expect(PalmCivet.to_bytes("5mib")).to eq(5 * PalmCivet::MEGABYTE) + expect(PalmCivet.to_bytes("2GiB")).to eq(2 * PalmCivet::GIGABYTE) + expect(PalmCivet.to_bytes("3TiB")).to eq(3 * PalmCivet::TERABYTE) + end + + it "allows whitespace before and after the value" do + expect(PalmCivet.to_bytes("\t\n\r 5MB ")).to eq(5 * PalmCivet::MEGABYTE) + end + + context 'when the byte amount is 0' do + it 'returns 0 bytes' do + expect(PalmCivet.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(PalmCivet.to_bytes('-200B')).to eq(-200) + end + end + + context "raises an error" do + it "when the unit is missing" do + expect { + PalmCivet.to_bytes("5") + }.to raise_error(PalmCivet::InvalidByteQuantityError) + end + + it "when the unit is unrecognized" do + expect { + PalmCivet.to_bytes("5MBB") + }.to raise_error(PalmCivet::InvalidByteQuantityError) + + expect { + PalmCivet.to_bytes("5BB") + }.to raise_error(PalmCivet::InvalidByteQuantityError) + end + end + end + + describe "#to_megabytes" do + it "parses byte amounts with short units (e.g. M, G)" do + expect(PalmCivet.to_megabytes("5B")).to eq(0) + expect(PalmCivet.to_megabytes("5K")).to eq(0) + expect(PalmCivet.to_megabytes("5M")).to eq(5) + expect(PalmCivet.to_megabytes("5m")).to eq(5) + expect(PalmCivet.to_megabytes("5G")).to eq(5120) + expect(PalmCivet.to_megabytes("5T")).to eq(5242880) + end + + it "parses byte amounts with long units (e.g MB, GB)" do + expect(PalmCivet.to_megabytes("5B")).to eq(0) + expect(PalmCivet.to_megabytes("5KB")).to eq(0) + expect(PalmCivet.to_megabytes("5MB")).to eq(5) + expect(PalmCivet.to_megabytes("5mb")).to eq(5) + expect(PalmCivet.to_megabytes("5GB")).to eq(5120) + expect(PalmCivet.to_megabytes("5TB")).to eq(5242880) + end + + it "parses byte amounts with long binary units (e.g MiB, GiB)" do + expect(PalmCivet.to_megabytes("5B")).to eq(0) + expect(PalmCivet.to_megabytes("5KiB")).to eq(0) + expect(PalmCivet.to_megabytes("5MiB")).to eq(5) + expect(PalmCivet.to_megabytes("5mib")).to eq(5) + expect(PalmCivet.to_megabytes("5GiB")).to eq(5120) + expect(PalmCivet.to_megabytes("5TiB")).to eq(5242880) + end + + it "allows whitespace before and after the value" do + expect(PalmCivet.to_megabytes("\t\n\r 5MB ")).to eq(5) + end + + context 'when the byte amount is 0' do + it 'returns 0 megabytes' do + expect(PalmCivet.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(PalmCivet.to_megabytes('-200MB')).to eq(-200) + end + end + + context "raises an error" do + it "when the unit is missing" do + expect { + PalmCivet.to_megabytes("5") + }.to raise_error(PalmCivet::InvalidByteQuantityError) + end + + it "when the unit is unrecognized" do + expect { + PalmCivet.to_megabytes("5MBB") + }.to raise_error(PalmCivet::InvalidByteQuantityError) + + expect { + PalmCivet.to_megabytes("5BB") + }.to raise_error(PalmCivet::InvalidByteQuantityError) + end + end + end + end +end