ManageIQ/manageiq-providers-amazon

View on GitHub
app/models/manageiq/providers/amazon/storage_manager/ebs/cloud_volume.rb

Summary

Maintainability
A
1 hr
Test Coverage
D
63%
class ManageIQ::Providers::Amazon::StorageManager::Ebs::CloudVolume < ::CloudVolume
  supports :create
  supports :delete do
    if !ext_management_system
      _("the volume is not connected to an active Provider")
    elsif status == "in-use"
      _("cannot delete volume that is in use.")
    end
  end
  supports :snapshot_create
  supports :update do
    _("the volume is not connected to an active provider") unless ext_management_system
  end
  supports :attach do
    if !ext_management_system
      _("the volume is not connected to an active Provider")
    elsif status != "available"
      _("the volume status is '%{status}' but should be 'available'") % {:status => status}
    end
  end
  supports :detach do
    if !ext_management_system
      _("the volume is not connected to an active Provider")
    elsif status != "in-use"
      _("the volume status is '%{status}' but should be 'in-use'") % {:status => status}
    end
  end

  CLOUD_VOLUME_TYPES = {
    :gp2      => N_('General Purpose SSD (GP2)'),
    :io1      => N_('Provisioned IOPS SSD (IO1)'),
    :st1      => N_('Throughput Optimized HDD (ST1)'),
    :sc1      => N_('Cold HDD (SC1)'),
    :standard => N_('Magnetic'),
  }.freeze

  def available_vms
    availability_zone.vms
  end

  def self.raw_create_volume(ext_management_system, options)
    options.symbolize_keys!
    options.delete(:ems_id)
    volume_name = options.delete(:name)

    availability_zone = ext_management_system.availability_zones.find_by(:id => options.delete(:availability_zone_id))
    options[:availability_zone] = availability_zone&.ems_ref

    volume = nil

    ext_management_system.with_provider_connection do |service|
      # Create the volume using provided options.
      volume = service.client.create_volume(options)
      # Also name the volume using tags.
      service.client.create_tags(
        :resources => [volume.volume_id],
        :tags      => [{ :key => "Name", :value => volume_name }]
      )
    end
    {:ems_ref => volume.volume_id, :status => volume.state, :name => volume_name}
  rescue => e
    _log.error "volume=[#{volume_name}], error: #{e}"
    raise MiqException::MiqVolumeCreateError, e.to_s, e.backtrace
  end

  def raw_update_volume(options)
    with_provider_object do |volume|
      # Update the name in case it was provided in the options.
      volume.create_tags(:tags => [{:key => "Name", :value => options[:name]}]) if options.key?(:name)

      # Mofiy volume configuration based on the given options.
      modify_opts = modify_volume_options(options)
      volume.client.modify_volume(modify_opts.merge(:volume_id => ems_ref)) unless modify_opts.empty?
    end
  rescue => e
    _log.error "volume=[#{name}], error: #{e}"
    raise MiqException::MiqVolumeUpdateError, e.to_s, e.backtrace
  end

  def raw_delete_volume
    with_provider_object(&:delete)
  rescue => e
    _log.error "volume=[#{name}], error: #{e}"
    raise MiqException::MiqVolumeDeleteError, e.to_s, e.backtrace
  end

  def raw_attach_volume(server_ems_ref, device = nil)
    with_provider_object do |vol|
      vol.attach_to_instance(:instance_id => server_ems_ref, :device => device)
    end
  end

  def raw_detach_volume(server_ems_ref)
    with_provider_object do |vol|
      vol.detach_from_instance(:instance_id => server_ems_ref)
    end
  end

  def provider_object(connection = nil)
    connection ||= ext_management_system.connect
    connection.volume(ems_ref)
  end

  def self.params_for_create(ems)
    {
      :fields => [
        {
          :component  => 'text-field',
          :name       => 'size',
          :id         => 'size',
          :label      => _('Size (in bytes)'),
          :type       => 'number',
          :step       => 1.gigabytes,
          :isRequired => true,
          :validate   => [{:type => 'required'}, {:type => 'min-number-value', :value => 0, :message => _('Size must be greater than or equal to 0')}],
          :condition  => {
            :not => {
              :when => 'volume_type',
              :is   => 'standard',
            },
          },
        },
        {
          :component    => 'select',
          :name         => 'availability_zone_id',
          :id           => 'availability_zone_id',
          :label        => _('Availability Zone'),
          :includeEmpty => true,
          :options      => ems.availability_zones.map do |az|
            {
              :label => az.name,
              :value => az.id,
            }
          end,
          :isRequired   => true,
          :validate     => [{:type => 'required'}],
        },
        {
          :component    => 'select',
          :name         => 'volume_type',
          :id           => 'volume_type',
          :label        => _('Cloud Volume Type'),
          :includeEmpty => true,
          :options      => CLOUD_VOLUME_TYPES.map do |value, label|
            option = {
              :label => _(label),
              :value => value,
            }

            # The standard (magnetic) volume_type is a special case. I can only be set upon creation
            # or when editing an entity that has its volume_type set to standard. Conditional options
            # are not supported by DDF, but resolveProps allows us to overwrite these options before
            # rendering.
            if value == :standard
              option[:condition] =
                {
                  :when => 'volume_type',
                  :is   => 'standard',
                }
            end

            option
          end,
          :isRequired   => true,
          :validate     => [{:type => 'required'}],
        },
        {
          :component => 'text-field',
          :name      => 'iops',
          :id        => 'iops',
          :label     => _('IOPS'),
          :type      => 'number',
          :condition => {
            :when => 'volume_type',
            :is   => 'io1',
          },
          :validate  => [{:type => 'min-number-value', :value => 0, :message => _('Number of IOPS Must be greater than or equal to 0')}],
        },
        {
          :component    => 'select',
          :name         => 'cloud_volume_snapshot_id',
          :id           => 'cloud_volume_snapshot_id',
          :label        => _('Base Snapshot'),
          :includeEmpty => true,
          :options      => ems.cloud_volume_snapshots.map do |cvs|
            {
              :value => cvs.id,
              :label => cvs.name,
            }
          end
        },
        {
          :component  => 'switch',
          :name       => 'encrypted',
          :id         => 'encrypted',
          :label      => _('Encrypted'),
          :onText     => _('Yes'),
          :offText    => _('No'),
          :isRequired => true,
        }
      ]
    }
  end

  def params_for_update
    {
      :fields => [
        {
          :component  => 'text-field',
          :name       => 'size',
          :id         => 'size',
          :label      => _('Size (in bytes)'),
          :type       => 'number',
          :step       => 1.gigabytes,
          :isDisabled => !!id,
          :isRequired => true,
          :validate   => [{:type => 'required'}, {:type => 'min-number-value', :value => 0, :message => _('Size must be greater than or equal to 0')}],
          :condition  => {
            :not => {
              :when => 'volume_type',
              :is   => 'standard',
            },
          },
        },
        {
          :component  => 'select',
          :name       => 'availability_zone_id',
          :id         => 'availability_zone_id',
          :label      => _('Availability Zone'),
          :isDisabled => !!id,
          :options    => ext_management_system.availability_zones.map do |az|
            {
              :label => az.name,
              :value => az.id,
            }
          end,
          :isRequired => true,
          :validate   => [{:type => 'required'}],
        },
        {
          :component  => 'select',
          :name       => 'volume_type',
          :id         => 'volume_type',
          :label      => _('Cloud Volume Type'),
          :isDisabled => !!id,
          :options    => CLOUD_VOLUME_TYPES.map do |value, label|
            option = {
              :label => _(label),
              :value => value,
            }

            # The standard (magnetic) volume_type is a special case. I can only be set upon creation
            # or when editing an entity that has its volume_type set to standard. Conditional options
            # are not supported by DDF, but resolveProps allows us to overwrite these options before
            # rendering.
            if value == :standard
              option[:condition] =
                {
                  :when => 'volume_type',
                  :is   => 'standard',
                }
            end
            option
          end,
          :isRequired => true,
          :validate   => [{:type => 'required'}],
        },
        {
          :component => 'text-field',
          :name      => 'iops',
          :id        => 'iops',
          :label     => _('IOPS'),
          :type      => 'number',
          :condition => {
            :when => 'volume_type',
            :is   => 'io1',
          },
          :validate  => [{:type => 'min-number-value', :value => 0, :message => _('Number of IOPS Must be greater than or equal to 0')}],
        },
        {
          :component  => 'select',
          :name       => 'cloud_volume_snapshot_id',
          :id         => 'cloud_volume_snapshot_id',
          :isDisabled => !!id,
          :label      => _('Base Snapshot'),
          :options    => base_snapshot.present? ? [{:value => base_snapshot.id, :label => base_snapshot.name}] : [{:value => nil, :label => _('Choose')}]
        },
        {
          :component  => 'switch',
          :name       => 'encrypted',
          :id         => 'encrypted',
          :isDisabled => !!id,
          :label      => _('Encrypted'),
          :onText     => _('Yes'),
          :offText    => _('No'),
          :isRequired => true,
        }
      ]
    }
  end

  def params_for_attach
    {
      :fields => [
        {
          :component  => 'text-field',
          :name       => 'device_mountpoint',
          :id         => 'device_mountpoint',
          :label      => _('Device Mountpoint'),
          :isRequired => true,
          :validate   => [{:type => 'required'}],
        },
      ]
    }
  end

  private

  def modify_volume_options(options = {})
    modify_opts = {}

    if volume_type != 'standard'
      modify_opts[:volume_type] = options[:volume_type] if options[:volume_type] && options[:volume_type] != volume_type
      modify_opts[:size]        = Integer(options[:size]) if options[:size] && Integer(options[:size]).gigabytes != size
      modify_opts[:iops]        = options[:iops] if (options[:volume_type] == "io1" || volume_type == 'io1') && options[:iops]
    end

    modify_opts
  end
end