Class: RightScale::Platform::VolumeManager
- Defined in:
- lib/right_agent/platform/linux.rb,
lib/right_agent/platform/darwin.rb,
lib/right_agent/platform/windows.rb
Overview
Provides utilities for managing volumes (disks).
Defined Under Namespace
Classes: ParserError, VolumeError
Instance Method Summary collapse
-
#assign_device(volume_device_or_index, device) ⇒ Object
Assigns the given device name to the volume given by index and clears the readonly attribute, if necessary.
-
#blocking_popen(command) ⇒ Object
Runs the specified command synchronously using IO.popen.
-
#disks(conditions = nil) ⇒ Object
Gets a list of physical or virtual disks in the form: [:status, :total_size, :free_size, :dynamic, :gpt*].
-
#format_disk(disk_index, device) ⇒ Object
Formats a disk given by disk index and the device (e.g. “D:”) for the volume on the primary NTFS partition which will be created.
-
#initialize ⇒ VolumeManager
constructor
A new instance of VolumeManager.
-
#is_attachable_volume_path?(path) ⇒ Boolean
Determines if the given path is valid for a Windows volume attachemnt (excluding the reserved A: B: C: drives).
-
#mount_volume(volume, mountpoint) ⇒ Object
Mounts a volume (returned by VolumeManager.volumes) to the mountpoint specified.
-
#online_disk(disk_index) ⇒ Object
Brings the disk given by index online and clears the readonly attribute, if necessary.
-
#partitions(disk_index, conditions = nil) ⇒ Object
Gets a list of partitions for the disk given by index in the form: :type, :size, :offset.
-
#volumes(conditions = nil) ⇒ Object
Gets a list of currently visible volumes in the form: [:device, :label, :filesystem, :type, :total_size, :status, :info*].
Constructor Details
#initialize ⇒ VolumeManager
Returns a new instance of VolumeManager.
208 209 210 |
# File 'lib/right_agent/platform/linux.rb', line 208 def initialize end |
Instance Method Details
#assign_device(volume_device_or_index, device) ⇒ Object
Assigns the given device name to the volume given by index and clears the readonly attribute, if necessary. The device must not currently be in use.
Parameters
- volume_device_or_index(int)
-
old device or zero-based volume index (from volumes list, etc.) to select for assignment.
- device(String)
-
device specified for the volume to create
Return
always true
Raise
- ArgumentError
-
on invalid parameters
- VolumeError
-
on failure to assign device name
- ParserError
-
on failure to parse volume list
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
# File 'lib/right_agent/platform/windows.rb', line 543 def assign_device(volume_device_or_index, device) volume_selector_match = volume_device_or_index.to_s.match(/^([D-Zd-z]|\d+):?$/) raise ArgumentError.new("Invalid volume_device_or_index = #{volume_device_or_index}") unless volume_selector_match volume_selector = volume_selector_match[1] raise ArgumentError.new("Invalid device = #{device}") unless is_attachable_volume_path?(device) new_letter = device[0,1] script = <<EOF rescan list volume select volume #{volume_selector} attribute volume clear readonly noerr assign letter=#{new_letter} EOF exit_code, output_text = run_script(script) raise VolumeError.new("Failed to assign device \"#{device}\" for volume \"#{volume_device_or_index}\": exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0 true end |
#blocking_popen(command) ⇒ Object
Runs the specified command synchronously using IO.popen
Parameters
- command(String)
-
system command to be executed
Return
- result(Array)
-
tuple of [exit_code, output_text]
332 333 334 335 336 337 338 |
# File 'lib/right_agent/platform/linux.rb', line 332 def blocking_popen(command) output_text = "" IO.popen(command) do |io| output_text = io.read end return $?.exitstatus, output_text end |
#disks(conditions = nil) ⇒ Object
Gets a list of physical or virtual disks in the form:
[{:index, :status, :total_size, :free_size, :dynamic, :gpt}*]
where
:index >= 0
:status = 'Online' | 'Offline'
:total_size = bytes used by partitions
:free_size = bytes not used by partitions
:dynamic = true | false
:gpt = true | false
GPT = GUID partition table
Parameters
- conditions{Hash)
-
hash of conditions to match or nil (default)
Return
- volumes(Array)
-
array of hashes detailing visible volumes.
Raise
- VolumeError
-
on failure to list disks
- ParserError
-
on failure to parse disks from output
363 364 365 366 367 368 369 370 371 |
# File 'lib/right_agent/platform/windows.rb', line 363 def disks(conditions = nil) script = <<EOF rescan list disk EOF exit_code, output_text = run_script(script) raise VolumeError.new("Failed to list disks: exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0 return parse_disks(output_text, conditions) end |
#format_disk(disk_index, device) ⇒ Object
Formats a disk given by disk index and the device (e.g. “D:”) for the volume on the primary NTFS partition which will be created.
Parameters
disk_index(int): zero-based disk index (from disks list, etc.)
- device(String)
-
device specified for the volume to create
Return
always true
Raise
- ArgumentError
-
on invalid parameters
- VolumeError
-
on failure to format
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/right_agent/platform/windows.rb', line 459 def format_disk(disk_index, device) # note that creating the primary partition automatically creates and # selects a new volume, which can be assigned a letter before the # partition has actually been formatted. raise ArgumentError.new("Invalid index = #{disk_index}") unless disk_index >= 0 raise ArgumentError.new("Invalid device = #{device}") unless is_attachable_volume_path?(device) letter = device[0,1] online_command = if @os_info.major < 6; "online noerr"; else; "online disk noerr"; end clear_readonly_command = if @os_info.major < 6; ""; else; "attribute disk clear readonly noerr"; end # note that Windows 2003 server version of diskpart doesn't support # format so that has to be done separately. format_command = if @os_info.major < 6; ""; else; "format FS=NTFS quick"; end script = <<EOF rescan list disk select disk #{disk_index} #{clear_readonly_command} #{online_command} clean create partition primary assign letter=#{letter} #{format_command} EOF exit_code, output_text = run_script(script) raise VolumeError.new("Failed to format disk #{disk_index} for device #{device}: exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0 # must format using command shell's FORMAT command before 2008 server. if @os_info.major < 6 command = "echo Y | format #{letter}: /Q /V: /FS:NTFS" output_text = `#{command}` exit_code = $?.exitstatus raise VolumeError.new("Failed to format disk #{disk_index} for device #{device}: exit code = #{exit_code}\n#{output_text}") if exit_code != 0 end true end |
#is_attachable_volume_path?(path) ⇒ Boolean
Determines if the given path is valid for a Windows volume attachemnt (excluding the reserved A: B: C: drives).
Return
- result(Boolean)
-
true if path is a valid volume root
337 338 339 |
# File 'lib/right_agent/platform/windows.rb', line 337 def is_attachable_volume_path?(path) return nil != (path =~ /^[D-Zd-z]:[\/\\]?$/) end |
#mount_volume(volume, mountpoint) ⇒ Object
Mounts a volume (returned by VolumeManager.volumes) to the mountpoint specified.
Parameters
- volume(Hash)
-
the volume hash returned by VolumeManager.volumes
- mountpoint(String)
-
the exact path where the device will be mounted ex: /mnt
Return
always true
Raise
- ArgumentError
-
on invalid parameters
- VolumeError
-
on a failure to mount the device
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/right_agent/platform/linux.rb', line 299 def mount_volume(volume, mountpoint) raise ArgumentError.new("Invalid volume = #{volume.inspect}") unless volume.is_a?(Hash) && volume[:device] exit_code, mount_list_output = blocking_popen('mount') raise VolumeError.new("Failed interrogation of current mounts; Exit Status: #{exit_code}\nError: #{mount_list_output}") unless exit_code == 0 device_match = /^#{volume[:device]} on (.+?)\s/.match(mount_list_output) mountpoint_from_device_match = device_match ? device_match[1] : mountpoint unless (mountpoint_from_device_match && mountpoint_from_device_match == mountpoint) raise VolumeError.new("Attempted to mount volume \"#{volume[:device]}\" at \"#{mountpoint}\" but it was already mounted at #{mountpoint_from_device_match}") end mountpoint_match = /^(.+?) on #{mountpoint}/.match(mount_list_output) device_from_mountpoint_match = mountpoint_match ? mountpoint_match[1] : volume[:device] unless (device_from_mountpoint_match && device_from_mountpoint_match == volume[:device]) raise VolumeError.new("Attempted to mount volume \"#{volume[:device]}\" at \"#{mountpoint}\" but \"#{device_from_mountpoint_match}\" was already mounted there.") end # The volume is already mounted at the correct mountpoint return true if /^#{volume[:device]} on #{mountpoint}/.match(mount_list_output) # TODO: Maybe validate that the mountpoint is valid *nix path? exit_code, mount_output = blocking_popen("mount -t #{volume[:filesystem].strip} #{volume[:device]} #{mountpoint}") raise VolumeError.new("Failed to mount volume to \"#{mountpoint}\" with device \"#{volume[:device]}\"; Exit Status: #{exit_code}\nError: #{mount_output}") unless exit_code == 0 return true end |
#online_disk(disk_index) ⇒ Object
Brings the disk given by index online and clears the readonly attribute, if necessary. The latter is required for some kinds of disks to online successfully and SAN volumes may be readonly when initially attached. As this change may bring additional volumes online the updated volumes list is returned.
Parameters
- disk_index(int)
-
zero-based disk index
Return
always true
Raise
- ArgumentError
-
on invalid parameters
- VolumeError
-
on failure to online disk
- ParserError
-
on failure to parse volume list
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'lib/right_agent/platform/windows.rb', line 512 def online_disk(disk_index) raise ArgumentError.new("Invalid disk_index = #{disk_index}") unless disk_index >= 0 clear_readonly_command = if @os_info.major < 6; ""; else; "attribute disk clear readonly noerr"; end online_command = if @os_info.major < 6; "online"; else; "online disk noerr"; end script = <<EOF rescan list disk select disk #{disk_index} #{clear_readonly_command} #{online_command} EOF exit_code, output_text = run_script(script) raise VolumeError.new("Failed to online disk #{disk_index}: exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0 true end |
#partitions(disk_index, conditions = nil) ⇒ Object
Gets a list of partitions for the disk given by index in the form:
{:index, :type, :size, :offset}
where
:index >= 0
:type = 'OEM' | 'Primary' | <undocumented>
:size = size in bytes used by partition on disk
:offset = offset of partition in bytes from head of disk
Parameters
- disk_index(int)
-
disk index to query
- conditions{Hash)
-
hash of conditions to match or nil (default)
Return
- result(Array)
-
list of partitions or empty
Raise
- VolumeError
-
on failure to list partitions
- ParserError
-
on failure to parse partitions from output
435 436 437 438 439 440 441 442 443 444 |
# File 'lib/right_agent/platform/windows.rb', line 435 def partitions(disk_index, conditions = nil) script = <<EOF rescan select disk #{disk_index} list partition EOF exit_code, output_text = run_script(script) raise VolumeError.new("Failed to list partitions exit code = #{exit_code}\n#{script}\n#{output_text}") if exit_code != 0 return parse_partitions(output_text, conditions) end |
#volumes(conditions = nil) ⇒ Object
Gets a list of currently visible volumes in the form:
[{:index, :device, :label, :filesystem, :type, :total_size, :status, :info}*]
where
:index >= 0
:device = "[A-Z]:"
:label = up to 11 characters
:filesystem = nil | 'NTFS' | <undocumented>
:type = 'NTFS' | <undocumented>
:total_size = size in bytes
:status = 'Healthy' | <undocumented>
:info = 'System' | empty | <undocumented>
note that a strange aspect of diskpart is that it won’t correlate disks to volumes in any list even though partition lists are always in the context of a selected disk.
volume order can change as volumes are created/destroyed between diskpart sessions so volume 0 can represent C: in one session and then be represented as volume 1 in the next call to diskpart.
volume labels are truncated to 11 characters by diskpart even though NTFS allows up to 32 characters.
Parameters
- conditions{Hash)
-
hash of conditions to match or nil (default)
Return
- volumes(Array)
-
array of hashes detailing visible volumes.
Raise
- VolumeError
-
on failure to list volumes
- ParserError
-
on failure to parse volumes from output
224 225 226 227 228 |
# File 'lib/right_agent/platform/linux.rb', line 224 def volumes(conditions = nil) exit_code, blkid_resp = blocking_popen('blkid') raise VolumeError.new("Failed to list volumes exit code = #{exit_code}\nblkid\n#{blkid_resp}") unless exit_code == 0 return parse_volumes(blkid_resp, conditions) end |