Class: RightScale::Platform::VolumeManager

Inherits:
PlatformHelperBase show all
Defined in:
lib/right_agent/platform.rb,
lib/right_agent/platform/unix/platform.rb,
lib/right_agent/platform/windows/platform.rb,
lib/right_agent/platform/unix/linux/platform.rb,
lib/right_agent/platform/unix/darwin/platform.rb

Overview

Provides utilities for managing volumes (disks).

Defined Under Namespace

Classes: ParserError, VolumeError

Constant Summary

Constants inherited from PlatformHelperBase

PlatformHelperBase::API_FALSE, PlatformHelperBase::API_NULL, PlatformHelperBase::API_TRUE, PlatformHelperBase::SIZEOF_DWORD, PlatformHelperBase::SIZEOF_QWORD, PlatformHelperBase::WIDE

Instance Method Summary collapse

Methods inherited from PlatformHelperBase

#copy_to_string_buffer, #with_unicode_buffer

Constructor Details

#initializeVolumeManager

Returns a new instance of VolumeManager.



576
577
578
579
# File 'lib/right_agent/platform/windows/platform.rb', line 576

def initialize
  @assignable_disk_regex = /^[D-Zd-z]:[\/\\]?$/
  @assignable_path_regex = /^[A-Za-z]:[\/\\][\/\\\w\s\d\-_\.~]+$/
end

Instance Method Details

#assign_device(volume_device_or_index, device, options = {}) ⇒ TrueClass

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 (Integer|String)

    as old device or zero-based volume index (from volumes list, etc.) to select for assignment.

  • device (String)

    as disk letter or mount path specified for the volume to create

  • options (Hash) (defaults to: {})

    for assignment

Options Hash (options):

  • :clear_readonly (TrueClass|FalseClass)

    if true will clear the volume readonly flag if set (Default = true)

  • :remove_all (TrueClass|FalseClass)

    if true will remove all previously assigned devices and paths, essentially a big RESET button for volume assignment (Default = false)

  • :idempotent (TrueClass|FalseClass)

    if true will checks the current device assignments before assigning the device according to the specified parameters and bails out if already assigned (Default = false)

Returns:

  • (TrueClass)

    always true

Raises:

  • (ArgumentError)

    on invalid parameters

  • (VolumeError)

    on failure to assign device name

  • (ParserError)

    on failure to parse volume list



823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
# File 'lib/right_agent/platform/windows/platform.rb', line 823

def assign_device(volume_device_or_index, device, options = {})
  # Set some defaults for backward compatibility, allow user specified options to override defaults
  options = {
    :clear_readonly => true,
    :remove_all     => false,
    :idempotent     => false
  }.merge(options)
  if device.match(@assignable_path_regex) && os_version.major < 6
    raise ArgumentError.new('Mount path assignment is not supported in this version of windows')
  end
  # Volume selector for drive letter assignments
  volume_selector_match = volume_device_or_index.to_s.match(/^([D-Zd-z]|\d+):?$/)
  # Volume selector for mount path assignments
  volume_selector_match = volume_device_or_index.to_s.match(@assignable_path_regex) unless volume_selector_match
  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)
  if options[:idempotent]
    # Device already assigned?
    already_assigned = volumes.any? do |volume|
      volume[:device] == device &&
      (volume[:index] == volume_device_or_index.to_s ||
       volume[:device] == volume_device_or_index.to_s)
    end
    return true if already_assigned
  end
  # Validation ends here, and real stuff starts to happen

  script = <<EOF
rescan
list volume
select volume "#{volume_selector}"
#{get_clear_readonly_command('volume') if options[:clear_readonly]}
#{'remove all noerr' if options[:remove_all]}
#{get_assign_command_for_device(device)}
EOF
  run_diskpart_script(script, 'assign device')
  true
end

#disks(conditions = nil) ⇒ Array

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) (defaults to: nil)

    to match or nil or empty (Default = no conditions)

Returns:

  • (Array)

    array of volume info hashes detailing visible disks

Raises:



608
609
610
611
612
613
614
615
# File 'lib/right_agent/platform/windows/platform.rb', line 608

def disks(conditions = nil)
  script = <<EOF
rescan
list disk
EOF
  output_text = run_diskpart_script(script, 'list disks')
  return parse_disks(output_text, conditions)
end

#format_disk(disk_index, device) ⇒ TrueClass

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 (Integer)

    as zero-based disk index (from disks list, etc.)

  • device (String)

    as disk letter or mount path specified for the volume to create

Returns:

  • (TrueClass)

    always true

Raises:

  • (ArgumentError)

    on invalid parameters

  • (VolumeError)

    on failure to format



692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
# File 'lib/right_agent/platform/windows/platform.rb', line 692

def format_disk(disk_index, device)
  if device.match(@assignable_path_regex) && os_version.major < 6
    raise ArgumentError.new("Mount path assignment is not supported in this version of windows")
  end
  # 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)

  # note that Windows 2003 server version of diskpart doesn't support
  # format so that has to be done separately.
  format_command = (os_version.major < 6) ? '' : 'format FS=NTFS quick'
  script = <<EOF
rescan
list disk
select disk #{disk_index}
#{get_clear_readonly_command('disk')}
#{get_online_disk_command}
clean
create partition primary
#{get_assign_command_for_device(device)}
#{format_command}
EOF
  run_diskpart_script(script, 'format disk')

  # must format using command shell's FORMAT command before 2008 server.
  if os_version.major < 6
    command = "echo Y | format #{device[0,1]}: /Q /V: /FS:NTFS"
    begin
      execute(command)
    rescue ::RightScale::Platform::CommandError => e
      raise VolumeError,
            "Failed to format disk #{disk_index} for device #{device}: #{e.message}\n#{e.output_text}"
    end
  end
  true
end

#is_attachable_volume_path?(path) ⇒ TrueClass|FalseClass

Determines if the given path is valid for a Windows volume attachemnt (excluding the reserved A: B: C: drives).

Returns:

  • (TrueClass|FalseClass)

    true if path is a valid volume root



585
586
587
# File 'lib/right_agent/platform/windows/platform.rb', line 585

def is_attachable_volume_path?(path)
   return (nil != (path =~ @assignable_disk_regex) || nil != (path =~ @assignable_path_regex))
end

#mount_volume(volume, mountpoint) ⇒ Object

Overrides base VolumeManager#mount_volume

Raises:

  • (::NotImplementedError)


146
147
148
# File 'lib/right_agent/platform/unix/platform.rb', line 146

def mount_volume(volume, mountpoint)
  must_be_overridden
end

#offline_disk(disk_index) ⇒ TrueClass

Brings the disk given by index offline

Parameters:

  • disk_index (Integer)

    as zero-based disk index

Returns:

  • (TrueClass)

    always true

Raises:



779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
# File 'lib/right_agent/platform/windows/platform.rb', line 779

def offline_disk(disk_index)
  raise ArgumentError.new("Invalid disk_index = #{disk_index}") unless disk_index >= 0
  if os_version.major < 6
    raise ::RightScale::Exceptions::PlatformError,
          'Offline disk is not supported by this platform'
  end

  # Set some defaults for backward compatibility, allow user specified options to override defaults
  script = <<EOF
rescan
list disk
select disk #{disk_index}
offline disk noerr
EOF

  run_diskpart_script(script, 'offline disk')
  true
end

#online_disk(disk_index, options = {}) ⇒ TrueClass

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 (Integer)

    as zero-based disk index

  • options (Hash) (defaults to: {})

    for online

Options Hash (options):

  • :idempotent (TrueClass|FalseClass)

    true to check the current disk statuses before attempting to online the disk and bails out when disk is already online (Default = false)

Returns:

  • (TrueClass)

    always true

Raises:

  • (ArgumentError)

    on invalid parameters

  • (VolumeError)

    on failure to online disk

  • (ParserError)

    on failure to parse disk list



748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
# File 'lib/right_agent/platform/windows/platform.rb', line 748

def online_disk(disk_index, options = {})
  raise ArgumentError.new("Invalid disk_index = #{disk_index}") unless disk_index >= 0
  # Set some defaults for backward compatibility, allow user specified options to override defaults
  options = { :idempotent => false }.merge(options)
  script = <<EOF
rescan
list disk
select disk #{disk_index}
#{get_clear_readonly_command('disk')}
#{get_online_disk_command}
EOF

  if options[:idempotent]
    disk = disks(:index => disk_index).first
    return true if disk && disk[:status] == 'Online'
  end

  run_diskpart_script(script, 'online disk')
  true
end

#partitions(disk_index, conditions = nil) ⇒ Array

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 (Integer)

    index to query

  • conditions (Hash) (defaults to: nil)

    to match or nil or empty (Default = no conditions)

Returns:

  • (Array)

    list of partitions or empty

Raises:

  • (VolumeError)

    on failure to list partitions

  • (ParserError)

    on failure to parse partitions from output



672
673
674
675
676
677
678
679
680
# File 'lib/right_agent/platform/windows/platform.rb', line 672

def partitions(disk_index, conditions = nil)
   script = <<EOF
rescan
select disk #{disk_index}
list partition
EOF
  output_text = run_diskpart_script(script, 'list partitions')
  return parse_partitions(output_text, conditions)
end

#volumes(conditions = nil) ⇒ Object

Overrides base VolumeManager#volumes

Raises:

  • (::NotImplementedError)


440
441
442
# File 'lib/right_agent/platform.rb', line 440

def volumes(conditions = nil)
  must_be_overridden
end