Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow adding disks to vm provision via api and automation #13318

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 34 additions & 13 deletions app/models/manageiq/providers/redhat/infra_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,45 @@ def vm_reconfigure(vm, options = {})
end

def add_disks(add_disks_spec, vm)
ems_storage_uid = add_disks_spec["ems_storage_uid"]
storage = add_disks_spec[:storage]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about backwards compatablity of this method?
I'm not sure from where it get's called though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was added recently for the sake of reconfigure vm - the disk spec is created internally only from app/models/manageiq/providers/redhat/infra_manager/vm/reconfigure.rb:

def build_config_spec(task_options)
    {
      "numCoresPerSocket" => (task_options[:cores_per_socket].to_i if task_options[:cores_per_socket]),
      "memoryMB"          => (task_options[:vm_memory].to_i if task_options[:vm_memory]),
      "numCPUs"           => (task_options[:number_of_cpus].to_i if task_options[:number_of_cpus]),
      "disksRemove"       => task_options[:disk_remove],
      "disksAdd"          => (spec_for_added_disks(task_options[:disk_add]) if task_options[:disk_add])
    }
  end

Therefore this is a legit spec change, without any risk to backward compatibility

with_disk_attachments_service(vm) do |service|
add_disks_spec["disks"].each { |disk_spec| service.add(prepare_disk(disk_spec, ems_storage_uid)) }
add_disks_spec[:disks].each { |disk_spec| service.add(prepare_disk(disk_spec, storage)) }
end
end

def prepare_disk(disk_spec, ems_storage_uid)
{
:bootable => disk_spec["bootable"],
:interface => "VIRTIO",
:active => true,
:disk => {
:provisioned_size => disk_spec["disk_size_in_mb"].to_i * 1024 * 1024,
:sparse => disk_spec["thin_provisioned"],
:format => disk_spec["format"],
:storage_domains => [:id => ems_storage_uid]
}
# prepare disk attachment request payload of adding disk for reconfigure vm
def prepare_disk(disk_spec, storage)
disk_spec = disk_spec.symbolize_keys
da_options = {
:size_in_mb => disk_spec[:disk_size_in_mb],
:storage => storage,
:name => disk_spec[:disk_name],
:thin_provisioned => disk_spec[:thin_provisioned],
:bootable => disk_spec[:bootable],
}

disk_attachment_builder = DiskAttachmentBuilder.new(da_options)
disk_attachment_builder.disk_attachment
end

# add disk to a virtual machine for a request arrived from an automation call
def vm_add_disk(vm, options = {})
storage = options[:datastore] || vm.storage
raise _("Data Store does not exist, unable to add disk") unless storage

da_options = {
:size_in_mb => options[:diskSize],
:storage => storage,
:name => options[:diskName],
:thin_provisioned => options[:thinProvisioned],
:bootable => options[:bootable],
:interface => options[:interface]
}

disk_attachment_builder = DiskAttachmentBuilder.new(da_options)
with_disk_attachments_service(vm) do |service|
service.add(disk_attachment_builder.disk_attachment)
end
end

def update_vm_memory(vm, virtual)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
class ManageIQ::Providers::Redhat::InfraManager::DiskAttachmentBuilder
def initialize(options = {})
@size_in_mb = options[:size_in_mb]
@storage = options[:storage]
@name = options[:name]
@thin_provisioned = BooleanParameter.new(options[:thin_provisioned])
@bootable = BooleanParameter.new(options[:bootable])
@active = options[:active]
@interface = options[:interface]
end

def disk_attachment
thin_provisioned = @thin_provisioned.true?
{
:bootable => @bootable.true?,
:interface => @interface || "VIRTIO",
:active => @active,
:disk => {
:name => @name,
:provisioned_size => @size_in_mb.to_i.megabytes,
:sparse => thin_provisioned,
:format => self.class.disk_format_for(@storage, thin_provisioned),
:storage_domains => [:id => ManageIQ::Providers::Redhat::InfraManager.extract_ems_ref_id(@storage.ems_ref)]
}
}
end

FILE_STORAGE_TYPE = %w(NFS GLUSTERFS VMFS).to_set.freeze
BLOCK_STORAGE_TYPE = %w(FCP ISCSI).to_set.freeze

def self.disk_format_for(storage, thin_provisioned)
if FILE_STORAGE_TYPE.include?(storage.store_type)
"raw"
elsif BLOCK_STORAGE_TYPE.include?(storage.store_type)
thin_provisioned ? "cow" : "raw"
else
"raw"
end
end

class BooleanParameter
def initialize(param)
@value = param.to_s == "true"
end

def true?
@value
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def monitor_events
end

def queue_event(event)
log.info "#{log_prefix} Caught event [#{event[:name]}]"
_log.info "#{log_prefix} Caught event [#{event[:name]}]"
EmsEvent.add_queue('add_rhevm', @cfg[:ems_id], event.to_hash)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class ManageIQ::Providers::Redhat::InfraManager::Provision < MiqProvision
include_concern 'Configuration'
include_concern 'Placement'
include_concern 'StateMachine'
include_concern 'Disk'

def destination_type
"Vm"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module ManageIQ::Providers::Redhat::InfraManager::Provision::Disk
def configure_dialog_disks
added_disks = options[:disk_scsi]
return nil if added_disks.blank?

options[:disks_add] = prepare_disks_for_add(added_disks)
end

def add_disks(disks)
destination.ext_management_system.with_disk_attachments_service(destination) do |service|
disks.each { |disk| service.add(disk) }
end
end

def destination_disks_locked?
destination.ext_management_system.with_provider_connection(:version => 4) do |connection|
system_service = connection.system_service
disks = system_service.vms_service.vm_service(destination.uid_ems).disk_attachments_service.list
disks.each do |disk|
fetched_disk = system_service.disks_service.disk_service(disk.id).get
return true unless fetched_disk.try(:status) == "ok"
end
end

false
end

private

def prepare_disks_for_add(disks_spec)
disks_spec.collect do |disk_spec|
disk = prepare_disk_for_add(disk_spec)
_log.info("disk: #{disk.inspect}")
disk
end.compact
end

def prepare_disk_for_add(disk_spec)
storage_name = disk_spec[:datastore]
raise MiqException::MiqProvisionError, "Storage is required for disk: <#{disk_spec.inspect}>" if storage_name.blank?

storage = Storage.find_by(:name => storage_name)
if storage.nil?
raise MiqException::MiqProvisionError, "Unable to find storage: <#{storage_name}> for disk: <#{disk_spec.inspect}>"
end

da_options = {
:size_in_mb => disk_spec[:sizeInMB],
:storage => storage,
:name => disk_spec[:filename],
:thin_provisioned => disk_spec[:backing] && disk_spec[:backing][:thinprovisioned],
:bootable => disk_spec[:bootable],
:interface => disk_spec[:interface]
}

disk_attachment_builder = ManageIQ::Providers::Redhat::InfraManager::DiskAttachmentBuilder.new(da_options)
disk_attachment_builder.disk_attachment
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ def customize_destination
_log.info("#{message} #{for_destination}")
update_and_notify_parent(:message => message)
configure_container
signal :configure_disks
end
end

def configure_disks
configure_dialog_disks
requested_disks = options[:disks_add]
if requested_disks.nil?
_log.info "Disks settings will be inherited from the template."
signal :customize_guest
else
add_disks(requested_disks)
signal :poll_add_disks_complete
end
end

def poll_add_disks_complete
update_and_notify_parent(:message => "Waiting for disks creation to complete for #{dest_name}")
if destination_disks_locked?
requeue_phase
else
signal :customize_guest
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,9 @@ def build_config_spec(task_options)
end

def spec_for_added_disks(disks)
disks.each { |disk_spec| disk_spec["format"] = disk_format_for(disk_spec["thin_provisioned"]) }
{
"disks" => disks,
"ems_storage_uid" => ManageIQ::Providers::Redhat::InfraManager.extract_ems_ref_id(storage.ems_ref),
:disks => disks,
:storage => storage
}
end

FILE_STORAGE_TYPE = %w(NFS GLUSTERFS VMFS).freeze
BLOCK_STORAGE_TYPE = %w(FCP ISCSI).freeze

def disk_format_for(thin_provisioned)
if FILE_STORAGE_TYPE.include?(storage.store_type)
"raw"
elsif BLOCK_STORAGE_TYPE.include?(storage.store_type)
thin_provisioned ? "cow" : "raw"
else
"raw"
end
end
end
9 changes: 8 additions & 1 deletion app/models/vm_or_template/operations/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,15 @@ def disconnect_floppies

def raw_add_disk(disk_name, disk_size_mb, options = {})
raise _("VM has no EMS, unable to add disk") unless ext_management_system
if options[:datastore]
datastore = Storage.find_by(:name => options[:datastore])
raise _("Data Store does not exist, unable to add disk") unless datastore
end

run_command_via_parent(:vm_add_disk, :diskName => disk_name, :diskSize => disk_size_mb,
:thinProvisioned => options[:thin_provisioned], :dependent => options[:dependent], :persistent => options[:persistent])
:thinProvisioned => options[:thin_provisioned], :dependent => options[:dependent],
:persistent => options[:persistent], :bootable => options[:bootable], :datastore => datastore,
:interface => options[:interface])
end

def add_disk(disk_name, disk_size_mb, options = {})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module MiqAeMethodService
class MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm < MiqAeServiceVmInfra
class MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm < MiqAeServiceManageIQ_Providers_InfraManager_Vm
def add_disk(disk_name, disk_size_mb, options = {})
sync_or_async_ems_operation(options[:sync], "add_disk", [disk_name, disk_size_mb, options])
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module MiqAeServiceManageIQ_Providers_Redhat_InfraManager_VmSpec
describe MiqAeMethodService::MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm do
let(:vm) { FactoryGirl.create(:vm_redhat) }
let(:service_vm) { MiqAeMethodService::MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm.find(vm.id) }

before do
allow(MiqServer).to receive(:my_zone).and_return('default')
@base_queue_options = {
:class_name => vm.class.name,
:instance_id => vm.id,
:zone => 'default',
:role => 'ems_operations',
:task_id => nil
}
end

it "#add_disk" do
service_vm.add_disk('disk_1', 100, :interface => "IDE", :bootable => true)

expect(MiqQueue.first).to have_attributes(
@base_queue_options.merge(
:method_name => 'add_disk',
:args => ['disk_1', 100, {:interface => "IDE", :bootable => true}])
)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
describe ManageIQ::Providers::Redhat::InfraManager::DiskAttachmentBuilder do
context "#disk_format_for" do
context "when storage type is file system" do
let(:storage) { FactoryGirl.build(:storage_nfs) }
it "returns 'raw' format for FS storage type" do
expect(described_class.disk_format_for(storage, false)).to eq("raw")
end

it "returns 'raw' format for thin provisioned" do
expect(described_class.disk_format_for(storage, true)).to eq("raw")
end
end

context "when storage type is block" do
let(:storage) { FactoryGirl.build(:storage_block) }

it "returns 'cow' format for block storage type and thin provisioned" do
expect(described_class.disk_format_for(storage, true)).to eq("cow")
end

it "returns 'raw' format for block storage type and thick provisioned" do
expect(described_class.disk_format_for(storage, false)).to eq("raw")
end
end

context "when storage type is not file system and not blcok" do
let(:storage) { FactoryGirl.build(:storage_unknown) }
it "returns 'raw' format as default" do
expect(described_class.disk_format_for(storage, false)).to eq("raw")
end
end
end

context "#disk_attachment" do
let(:storage) { FactoryGirl.build(:storage_nfs, :ems_ref => "http://example.com/storages/XYZ") }

it "creates disk attachment" do
builder = described_class.new(:size_in_mb => 10, :storage => storage, :name => "disk-1",
:thin_provisioned => true, :bootable => true, :active => false, :interface => "IDE")
expected_disk_attachment = {
:bootable => true,
:interface => "IDE",
:active => false,
:disk => {
:name => "disk-1",
:provisioned_size => 10 * 1024 * 1024,
:sparse => true,
:format => "raw",
:storage_domains => [:id => "XYZ"]
}
}

expect(builder.disk_attachment).to eq(expected_disk_attachment)
end
end

describe ManageIQ::Providers::Redhat::InfraManager::DiskAttachmentBuilder::BooleanParameter do
let(:param) { nil }
subject { described_class.new(param).true? }

context "param is true" do
let(:param) { true }

it { is_expected.to be_truthy }
end

context "param is false" do
let(:param) { false }

it { is_expected.to be_falsey }
end

context "param is 'true'" do
let(:param) { "true" }

it { is_expected.to be_truthy }
end

context "param is 'false'" do
let(:param) { "false" }

it { is_expected.to be_falsey }
end

context "param is nil" do
it { is_expected.to be_falsey }
end

context "param is 'invalid'" do
let(:param) { 'invalid' }

it { is_expected.to be_falsey }
end
end
end
Loading