Class: Pvectl::Repositories::Storage

Inherits:
Base
  • Object
show all
Defined in:
lib/pvectl/repositories/storage.rb,
sig/pvectl/repositories/storage.rbs

Overview

Repository for Proxmox cluster storage pools.

Uses the /cluster/resources?type=storage API endpoint for cluster-wide view. Handles aggregation of shared storage (deduplication by name).

Examples:

Listing all storage pools

repo = Storage.new(connection)
storage_pools = repo.list
storage_pools.each { |s| puts "#{s.name}: #{s.plugintype}" }

Listing storage on a specific node

storage_pools = repo.list(node: "pve1")

See Also:

Instance Attribute Summary

Attributes inherited from Base

#connection

Instance Method Summary collapse

Methods inherited from Base

#extract_data, #initialize, #models_from, #unwrap

Constructor Details

This class inherits a constructor from Pvectl::Repositories::Base

Instance Method Details

#aggregate_storage(storage_data) ⇒ Array<Hash>

Aggregates storage data by name. For shared storage, keeps first entry (data is identical across nodes). For local storage, keeps all entries.

Parameters:

  • storage_data (Array<Hash>)

    raw storage data from API

Returns:

  • (Array<Hash>)

    aggregated storage data



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/pvectl/repositories/storage.rb', line 283

def aggregate_storage(storage_data)
  seen = {}
  storage_data.each do |data|
    name = data[:storage]
    next if name.nil?

    # For shared storage, keep only first entry
    if data[:shared] == 1
      seen[name] ||= data
    else
      # For local storage, include all (unique by name+node)
      key = "#{name}:#{data[:node]}"
      seen[key] ||= data
    end
  end
  seen.values
end

#build_describe_model(storage, config, status, content) ⇒ Models::Storage

Builds Storage model with comprehensive describe data.

Merges data from basic storage, config, status, and content endpoints.

Parameters:

  • storage (Models::Storage)

    base storage model

  • config (Hash)

    configuration from /storage/name

  • status (Hash)

    status from /nodes/node/storage/name/status

  • content (Array<Hash>)

    volumes from /content endpoint

Returns:



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/pvectl/repositories/storage.rb', line 230

def build_describe_model(storage, config, status, content)
  Models::Storage.new(
    # Basic attributes from list
    name: storage.name,
    plugintype: storage.plugintype,
    status: storage.status,
    node: storage.node,
    disk: storage.disk,
    maxdisk: storage.maxdisk,
    content: storage.content,
    shared: storage.shared,

    # Config attributes
    path: config[:path],
    server: config[:server],
    export: config[:export],
    pool: config[:pool],
    vgname: config[:vgname],
    thinpool: config[:thinpool],
    nodes: config[:nodes],
    "prune-backups": config[:"prune-backups"],
    maxfiles: config[:maxfiles],

    # Status attributes (override if available)
    avail: status[:avail] || storage.avail,
    enabled: status[:enabled] || storage.enabled,
    active: status[:active] || storage.active_flag,

    # Content (volumes)
    volumes: content
  )
end

#build_model(data) ⇒ Models::Storage

Builds Storage model from API response data.

Parameters:

  • data (Hash)

    API response hash

Returns:



124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/pvectl/repositories/storage.rb', line 124

def build_model(data)
  Models::Storage.new(
    name: data[:storage],
    plugintype: data[:plugintype],
    status: data[:status],
    node: data[:node],
    disk: data[:disk],
    maxdisk: data[:maxdisk],
    content: data[:content],
    shared: data[:shared]
  )
end

#build_model_from_node_api(data, node_name) ⇒ Models::Storage

Builds Storage model from /nodes/node/storage API response.

Maps node-specific API fields to model attributes:

  • type -> plugintype
  • used -> disk
  • total -> maxdisk
  • active -> status (derived)

Parameters:

  • data (Hash)

    API response hash

  • node_name (String)

    node name (not in response, passed as param)

Returns:



148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/pvectl/repositories/storage.rb', line 148

def build_model_from_node_api(data, node_name)
  Models::Storage.new(
    name: data[:storage],
    plugintype: data[:type],
    node: node_name,
    disk: data[:used],
    maxdisk: data[:total],
    avail: data[:avail],
    content: data[:content],
    enabled: data[:enabled],
    active: data[:active],
    shared: 0 # /nodes/{node}/storage doesn't return shared flag
  )
end

#describe(name, node: nil) ⇒ Models::Storage?

Describes a storage with comprehensive details from multiple API endpoints.

Fetches:

  • Basic storage info from cluster resources (via get or get_for_node)
  • Configuration from /storage/name
  • Status from /nodes/node/storage/name/status
  • Content (volumes) from /nodes/node/storage/name/content

Parameters:

  • name (String)

    storage name

  • node (String, nil) (defaults to: nil)

    specific node for local storage

  • node: (String, nil) (defaults to: nil)

Returns:

  • (Models::Storage, nil)

    Storage model with full details, or nil if not found



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/pvectl/repositories/storage.rb', line 85

def describe(name, node: nil)
  storage = node ? get_for_node(name, node) : get(name)
  return nil unless storage

  # GET /storage/{name} - configuration
  config = fetch_storage_config(name)

  # Find active node for this storage
  node = find_node_for_storage(name, storage)

  # GET /nodes/{node}/storage/{name}/status (if node available)
  status = node ? fetch_storage_status(node, name) : {}

  # GET /nodes/{node}/storage/{name}/content (volumes)
  content = node ? fetch_storage_content(node, name) : []

  build_describe_model(storage, config, status, content)
end

#fetch_storage_config(name) ⇒ Hash

Fetches storage configuration from /storage/name.

Parameters:

  • name (String)

    storage name

Returns:

  • (Hash)

    configuration data or empty hash on error



169
170
171
172
173
174
# File 'lib/pvectl/repositories/storage.rb', line 169

def fetch_storage_config(name)
  response = connection.client["storage/#{name}"].get
  extract_data(response)
rescue StandardError
  {}
end

#fetch_storage_content(node, name) ⇒ Array<Hash>

Fetches storage content (volumes) from /nodes/node/storage/name/content.

Parameters:

  • node (String)

    node name

  • name (String)

    storage name

Returns:

  • (Array<Hash>)

    volumes array or empty array on error



193
194
195
196
197
198
# File 'lib/pvectl/repositories/storage.rb', line 193

def fetch_storage_content(node, name)
  response = connection.client["nodes/#{node}/storage/#{name}/content"].get
  unwrap(response)
rescue StandardError
  []
end

#fetch_storage_status(node, name) ⇒ Hash

Fetches storage status from /nodes/node/storage/name/status.

Parameters:

  • node (String)

    node name

  • name (String)

    storage name

Returns:

  • (Hash)

    status data or empty hash on error



181
182
183
184
185
186
# File 'lib/pvectl/repositories/storage.rb', line 181

def fetch_storage_status(node, name)
  response = connection.client["nodes/#{node}/storage/#{name}/status"].get
  extract_data(response)
rescue StandardError
  {}
end

#find_node_for_storage(name, storage) ⇒ String?

Finds an active node where this storage is accessible.

For local storage: uses the node it belongs to. For shared storage: finds first online node in the cluster.

Parameters:

  • name (String)

    storage name

  • storage (Models::Storage)

    storage model

Returns:

  • (String, nil)

    node name or nil if unavailable



208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/pvectl/repositories/storage.rb', line 208

def find_node_for_storage(name, storage)
  # For local storage, use the node it belongs to
  return storage.node unless storage.shared?

  # For shared storage, find first available online node
  nodes_response = connection.client["nodes"].get
  nodes = unwrap(nodes_response)
  online_node = nodes.find { |n| n[:status] == "online" }
  online_node&.dig(:node)
rescue StandardError
  nil
end

#get(name) ⇒ Models::Storage?

Gets a single storage pool by name.

Parameters:

  • name (String)

    storage pool name

Returns:



48
49
50
# File 'lib/pvectl/repositories/storage.rb', line 48

def get(name)
  list.find { |s| s.name == name }
end

#get_for_node(name, node) ⇒ Models::Storage?

Gets storage for a specific node.

Parameters:

  • name (String)

    storage name

  • node (String)

    node name

Returns:



70
71
72
# File 'lib/pvectl/repositories/storage.rb', line 70

def get_for_node(name, node)
  list_instances(name).find { |s| s.node == node }
end

#list(node: nil) ⇒ Array<Models::Storage>

Lists all storage pools in the cluster.

Uses /cluster/resources?type=storage endpoint for cluster-wide view. Aggregates shared storage by keeping first entry per storage name.

Parameters:

  • node (String, nil) (defaults to: nil)

    filter by node name

  • node: (String, nil) (defaults to: nil)

Returns:



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/pvectl/repositories/storage.rb', line 29

def list(node: nil)
  response = connection.client["cluster/resources"].get(params: { type: "storage" })
  storage_data = normalize_response(response)

  # Aggregate shared storage (deduplicate by name, keep first entry)
  aggregated = aggregate_storage(storage_data)

  # Filter by node if specified
  if node
    aggregated = aggregated.select { |data| data[:node] == node || data[:shared] == 1 }
  end

  aggregated.map { |data| build_model(data) }
end

#list_for_node(node_name) ⇒ Array<Models::Storage>

Lists storage pools for a specific node.

Uses /nodes/{node}/storage endpoint which returns detailed per-node storage information including avail, enabled, active flags.

Parameters:

  • node_name (String)

    node name

Returns:



111
112
113
114
115
116
# File 'lib/pvectl/repositories/storage.rb', line 111

def list_for_node(node_name)
  response = connection.client["nodes/#{node_name}/storage"].get
  storage_data = normalize_response(response)

  storage_data.map { |data| build_model_from_node_api(data, node_name) }
end

#list_instances(name) ⇒ Array<Models::Storage>

Lists all instances of a storage by name.

For shared storage: returns single instance. For local storage: returns all instances (one per node).

Parameters:

  • name (String)

    storage name

Returns:



59
60
61
62
63
# File 'lib/pvectl/repositories/storage.rb', line 59

def list_instances(name)
  response = connection.client["cluster/resources"].get(params: { type: "storage" })
  storage_data = normalize_response(response)
  storage_data.select { |s| s[:storage] == name }.map { |data| build_model(data) }
end

#normalize_response(response) ⇒ Array<Hash>

Normalizes API response to array format.

Parameters:

  • response (Array, Hash)

    API response

Returns:

  • (Array<Hash>)

    array of storage data hashes



267
268
269
270
271
272
273
274
275
# File 'lib/pvectl/repositories/storage.rb', line 267

def normalize_response(response)
  if response.is_a?(Array)
    response
  elsif response.is_a?(Hash) && response[:data]
    response[:data]
  else
    response.to_a
  end
end