Class: Pvectl::Services::ResizeVolume

Inherits:
Object
  • Object
show all
Defined in:
lib/pvectl/services/resize_volume.rb

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:

  1. #preflight — validates volume exists, computes new size, checks constraints

  2. #perform — executes the actual resize via repository

Examples:

Basic resize flow

parsed = ResizeVolume.parse_size("+10G")
service = ResizeVolume.new(repository: vm_repo)
info = service.preflight(100, "scsi0", parsed, node: "pve1")
result = service.perform(100, "scsi0", parsed.raw, node: "pve1")

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

Instance Method Summary collapse

Constructor Details

#initialize(repository:) ⇒ ResizeVolume

Creates a new ResizeVolume service.

Parameters:



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.

Examples:

Relative size

ResizeVolume.parse_size("+10G")
#=> ParsedSize(relative: true, value: "10G", raw: "+10G")

Absolute size

ResizeVolume.parse_size("50G")
#=> ParsedSize(relative: false, value: "50G", raw: "50G")

Parameters:

  • size_str (String)

    size string to parse

Returns:

Raises:

  • (ArgumentError)

    if format is invalid, empty, or negative



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

#perform(id, disk, raw_size, node:) ⇒ Models::OperationResult

Executes the disk resize via repository.

Parameters:

  • id (Integer)

    resource identifier (VMID or CTID)

  • disk (String)

    disk key

  • raw_size (String)

    size string for API (e.g., “+10G”, “50G”)

  • node (String)

    node name

Returns:



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.message,
    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).

Parameters:

  • id (Integer)

    resource identifier (VMID or CTID)

  • disk (String)

    disk key (e.g., “scsi0”, “rootfs”, “mp0”)

  • parsed_size (ParsedSize)

    parsed size from parse_size

  • node (String)

    node name

Returns:

  • (Hash)

    preflight info with :disk, :current_size, :new_size

Raises:



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