Class: Pvectl::Services::ResizeVolume
- Inherits:
-
Object
- Object
- Pvectl::Services::ResizeVolume
- Defined in:
- lib/pvectl/services/resize_volume.rb,
sig/pvectl/services/resize_volume.rbs
Overview
Orchestrates volume resize operations for VMs and containers.
Handles size parsing, preflight validation (current size comparison), and execution via the repository interface. Works polymorphically with both VM and Container repositories (they share #fetch_config and #resize).
Two-phase operation:
- #preflight — validates volume exists, computes new size, checks constraints
- #perform — executes the actual resize via repository
Defined Under Namespace
Classes: ParsedSize, SizeTooSmallError, VolumeNotFoundError
Constant Summary collapse
- SIZE_PATTERN =
Size regex: optional +, digits with optional decimal, optional T/G/M/K suffix.
/\A(\+)?(\d+(?:\.\d+)?)([TGMK])?\z/i- UNIT_MULTIPLIERS =
Multipliers for converting units to megabytes (MB as base unit).
{ "T" => 1024 * 1024, "G" => 1024, "M" => 1, "K" => 1.0 / 1024 }.freeze
Class Method Summary collapse
-
.parse_size(size_str) ⇒ ParsedSize
Parses a size string into a ParsedSize.
Instance Method Summary collapse
-
#calculate_new_size(current_size, parsed_size) ⇒ String
Calculates the new size after resize.
-
#convert_to_unit(value, from_unit, to_unit) ⇒ Float
Converts a value from one unit to another using MB as base.
-
#extract_disk_size(config, disk, id) ⇒ String
Extracts the disk size from a config value string.
-
#format_size(value, suffix) ⇒ String
Formats a numeric value and suffix into a size string.
-
#initialize(repository:) ⇒ ResizeVolume
constructor
Creates a new ResizeVolume service.
-
#parse_size_components(size) ⇒ Array(Float, String)
Parses size string into numeric value and unit suffix.
-
#perform(id, disk, raw_size, node:) ⇒ Models::OperationResult
Executes the disk resize via repository.
-
#preflight(id, disk, parsed_size, node:) ⇒ Hash
Validates the resize operation and returns size information.
-
#size_to_bytes(size) ⇒ Float
Converts a size string to bytes for comparison.
-
#validate_new_size!(current_size, new_size, parsed_size) ⇒ void
Validates that the new size is larger than current for absolute resizes.
Constructor Details
#initialize(repository:) ⇒ ResizeVolume
Creates a new ResizeVolume service.
96 97 98 |
# File 'lib/pvectl/services/resize_volume.rb', line 96 def initialize(repository:) @repository = repository end |
Class Method Details
.parse_size(size_str) ⇒ ParsedSize
Parses a size string into a ParsedSize.
Accepts formats like "10G", "+10G", "1.5T", "512M", "+100". Suffix is uppercased. No suffix means raw number.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/pvectl/services/resize_volume.rb', line 72 def self.parse_size(size_str) raise ArgumentError, "Size cannot be empty" if size_str.nil? || size_str.strip.empty? match = SIZE_PATTERN.match(size_str.strip) raise ArgumentError, "Invalid size format: #{size_str}" unless match plus, number, suffix = match.captures suffix = suffix&.upcase raise ArgumentError, "Size must be positive: #{size_str}" if number.to_f <= 0 clean_value = "#{number}#{suffix}" raw_value = "#{plus}#{number}#{suffix}" ParsedSize.new( relative: !plus.nil?, value: clean_value, raw: raw_value ) end |
Instance Method Details
#calculate_new_size(current_size, parsed_size) ⇒ String
Calculates the new size after resize.
For relative sizes, adds the increment to current size (converting units as needed). For absolute sizes, returns the parsed value directly.
177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/pvectl/services/resize_volume.rb', line 177 def calculate_new_size(current_size, parsed_size) if parsed_size.relative? current_num, current_suffix = parse_size_components(current_size) add_num, add_suffix = parse_size_components(parsed_size.value) converted_add = convert_to_unit(add_num, add_suffix, current_suffix) new_num = current_num + converted_add format_size(new_num, current_suffix) else parsed_size.value end end |
#convert_to_unit(value, from_unit, to_unit) ⇒ Float
Converts a value from one unit to another using MB as base.
226 227 228 229 |
# File 'lib/pvectl/services/resize_volume.rb', line 226 def convert_to_unit(value, from_unit, to_unit) mb_value = value * UNIT_MULTIPLIERS.fetch(from_unit, 1024) mb_value / UNIT_MULTIPLIERS.fetch(to_unit, 1024) end |
#extract_disk_size(config, disk, id) ⇒ String
Extracts the disk size from a config value string.
Config values have formats like:
- VM: "local-lvm:vm-100-disk-0,size=32G"
- Container: "local-lvm:subvol-100-disk-0,size=8G"
- Rootfs: "local-lvm:subvol-100-disk-0,size=8G"
159 160 161 162 163 164 165 166 167 |
# File 'lib/pvectl/services/resize_volume.rb', line 159 def extract_disk_size(config, disk, id) disk_value = config[disk.to_sym] raise VolumeNotFoundError, "Volume '#{disk}' not found in config for resource #{id}" unless disk_value size_match = disk_value.to_s.match(/size=(\d+(?:\.\d+)?[TGMK]?)/i) raise VolumeNotFoundError, "Cannot determine size for volume '#{disk}' on resource #{id}" unless size_match size_match[1] end |
#format_size(value, suffix) ⇒ String
Formats a numeric value and suffix into a size string.
Produces integer format when possible (e.g., "42G" not "42.0G").
238 239 240 241 |
# File 'lib/pvectl/services/resize_volume.rb', line 238 def format_size(value, suffix) formatted = value == value.to_i ? value.to_i.to_s : format("%.1f", value) "#{formatted}#{suffix}" end |
#parse_size_components(size) ⇒ Array(Float, String)
Parses size string into numeric value and unit suffix.
213 214 215 216 217 218 |
# File 'lib/pvectl/services/resize_volume.rb', line 213 def parse_size_components(size) match = size.to_s.match(/\A(\d+(?:\.\d+)?)([TGMK])?\z/i) return [0.0, "G"] unless match [match[1].to_f, (match[2] || "G").upcase] end |
#perform(id, disk, raw_size, node:) ⇒ Models::OperationResult
Executes the disk resize via repository.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/pvectl/services/resize_volume.rb', line 129 def perform(id, disk, raw_size, node:) @repository.resize(id, node, disk: disk, size: raw_size) Models::OperationResult.new( operation: :resize_volume, success: true, resource: { id: id, node: node, disk: disk, size: raw_size } ) rescue StandardError => e Models::OperationResult.new( operation: :resize_volume, success: false, error: e., resource: { id: id, node: node, disk: disk } ) end |
#preflight(id, disk, parsed_size, node:) ⇒ Hash
Validates the resize operation and returns size information.
Checks that the disk exists in the resource config, extracts current size, calculates new size, and validates constraints (absolute must be larger than current).
113 114 115 116 117 118 119 120 |
# File 'lib/pvectl/services/resize_volume.rb', line 113 def preflight(id, disk, parsed_size, node:) config = @repository.fetch_config(node, id) current_size = extract_disk_size(config, disk, id) new_size = calculate_new_size(current_size, parsed_size) validate_new_size!(current_size, new_size, parsed_size) { disk: disk, current_size: current_size, new_size: new_size } end |
#size_to_bytes(size) ⇒ Float
Converts a size string to bytes for comparison.
247 248 249 250 |
# File 'lib/pvectl/services/resize_volume.rb', line 247 def size_to_bytes(size) num, suffix = parse_size_components(size) num * UNIT_MULTIPLIERS.fetch(suffix, 1024) * 1024 * 1024 # MB to bytes end |
#validate_new_size!(current_size, new_size, parsed_size) ⇒ void
This method returns an undefined value.
Validates that the new size is larger than current for absolute resizes.
Relative sizes always pass (Proxmox enforces positive increments). Absolute sizes must be strictly larger than current.
200 201 202 203 204 205 206 207 |
# File 'lib/pvectl/services/resize_volume.rb', line 200 def validate_new_size!(current_size, new_size, parsed_size) return if parsed_size.relative? if size_to_bytes(new_size) <= size_to_bytes(current_size) raise SizeTooSmallError, "New size #{new_size} must be larger than current size #{current_size}" end end |