Skip to content

Commit

Permalink
Support CPU/MEM/HDD reconfiguration
Browse files Browse the repository at this point in the history
With this commit we implement whatever support for virtual hardware
reconfiguration. Namely, we support following operations for VM:

- increase/decrease memory
- increase/decrease number of cpu cores and sockets
- increase disk size
- add/remove disk

BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1572086

Signed-off-by: Miha Pleško <miha.plesko@xlab.si>
  • Loading branch information
miha-plesko committed May 17, 2018
1 parent 41b450f commit 47e6b79
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 0 deletions.
8 changes: 8 additions & 0 deletions app/models/manageiq/providers/vmware/cloud_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,12 @@ def vm_remove_all_snapshots(vm, _options = {})
service.process_task(response.body)
end
end

def vm_reconfigure(vm, options = {})
with_provider_connection do |service|
xml = service.get_vapp(vm.ems_ref, :parser => 'xml').body
response = service.post_reconfigure_vm(vm.ems_ref, xml, options[:spec])
service.process_task(response.body)
end
end
end
5 changes: 5 additions & 0 deletions app/models/manageiq/providers/vmware/cloud_manager/vm.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
class ManageIQ::Providers::Vmware::CloudManager::Vm < ManageIQ::Providers::CloudManager::Vm
include_concern 'Operations'
include_concern 'RemoteConsole'
include_concern 'Reconfigure'

supports :snapshots
supports :remove_all_snapshots
supports_not :remove_snapshot
supports :snapshot_create
supports :revert_to_snapshot
supports :reconfigure_disks
supports :reconfigure_disksize do
unsupported_reason_add(:reconfigure_disksize, 'Cannot resize disks of a VM with snapshots') unless snapshots.empty?
end

def provider_object(connection = nil)
connection ||= ext_management_system.connect
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
module ManageIQ::Providers::Vmware::CloudManager::Vm::Reconfigure
# Show Reconfigure VM task
def reconfigurable?
true
end

def max_cpu_cores_per_socket(_total_vcpus = nil)
128
end

def max_total_vcpus
128
end

def max_vcpus
128
end

def max_memory_mb
4.terabyte / 1.megabyte
end

def disk_types
['LSI Logic Parallel SCSI']
end

def disk_default_type
'LSI Logic Parallel SCSI'
end

def validate_config_spec(options)
if vm_powered_on?
if options[:number_of_cpus]
number_of_cpus = options[:number_of_cpus].to_i
cores_per_socket = options[:cores_per_socket].to_i
raise MiqException::MiqVmError, 'CPU Hot-Add not enabled' if number_of_cpus != cpu_total_cores && !cpu_hot_add_enabled
raise MiqException::MiqVmError, 'CPU Hot-Remove not enabled' if number_of_cpus < cpu_total_cores && !cpu_hot_remove_enabled
raise MiqException::MiqVmError, 'Cannot change CPU cores per socket on a running VM' if cores_per_socket != cpu_cores_per_socket
end

if options[:vm_memory]
vm_memory = options[:vm_memory].to_i
raise MiqException::MiqVmError, 'Memory Hot-Add not enabled' if vm_memory > ram_size && !memory_hot_add_enabled
raise MiqException::MiqVmError, 'Cannot remove memory from a running VM' if vm_memory < ram_size
end
end

raise MiqException::MiqVmError, 'Cannot resize disk of VM with shapshots' if options[:disk_resize] && !snapshots.empty?
end

def build_config_spec(options)
validate_config_spec(options)

# Virtual hardware modifications.
new_hw = {}
new_hw[:memory] = { :quantity_mb => options[:vm_memory] } if options[:vm_memory]
new_hw[:cpu] = { :num_cores => options[:number_of_cpus], :cores_per_socket => options[:cores_per_socket] } if options[:number_of_cpus]
if (%i(disk_add disk_resize disk_remove) & options.keys).any?
new_hw[:disk] = []
Array(options[:disk_add]) .each_with_object(new_hw[:disk]) { |d, res| res << { :capacity_mb => d[:disk_size_in_mb].to_i } }
Array(options[:disk_resize]).each_with_object(new_hw[:disk]) { |d, res| res << { :id => disk_id(d[:disk_name]), :capacity_mb => d[:disk_size_in_mb].to_i } }
Array(options[:disk_remove]).each_with_object(new_hw[:disk]) { |d, res| res << { :id => disk_id(d[:disk_name]), :capacity_mb => -1 } }
end

new_hw.empty? ? {} : { :hardware => new_hw }
end

def disk_id(disk_name)
disk = disks.detect { |d| d.filename == disk_name }
# Disk location is stored as "{addr}/{parent_addr}/{disk_id}" e.g. "0/3/2000"
disk.location.to_s.split('/').last
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
describe ManageIQ::Providers::Vmware::CloudManager::Vm::Reconfigure do
let(:vm) do
FactoryGirl.create(
:vm_vmware_cloud,
:name => 'test_vm',
:raw_power_state => 'off',
:cpu_hot_add_enabled => true,
:cpu_hot_remove_enabled => true,
:memory_hot_add_enabled => true,
:hardware => FactoryGirl.create(
:hardware,
:cpu4x2,
:ram1GB,
:disks => [
FactoryGirl.create(:disk, :size => 1024, :filename => 'Disk 0', :location => '0/1/2000'),
FactoryGirl.create(:disk, :size => 2048, :filename => 'Disk 1', :location => '0/2/2001'),
]
)
)
end

it '#reconfigurable?' do
expect(vm.reconfigurable?).to be_truthy
end

describe '#build_config_spec' do
let(:fog_options) { vm.build_config_spec(options) }
let(:options) do
{
:vm_memory => 16_384,
:cores_per_socket => 2,
:number_of_cpus => 16,
:disk_add => [{ :disk_size_in_mb => '4096' }],
:disk_resize => [{ :disk_name => 'Disk 0', :disk_size_in_mb => '6144' }],
:disk_remove => [{ :disk_name => 'Disk 1' }],
}
end

describe 'no hardware changes' do
let(:options) { {} }

it 'fog request optimized' do
expect(fog_options).to eq({})
end
end

context 'VM off' do
it 'memory' do
expect(fog_options[:hardware][:memory][:quantity_mb]).to eq(16_384)
end

describe 'cpu' do
it 'num_cores' do
expect(fog_options[:hardware][:cpu][:num_cores]).to eq(16)
end

it 'cores_per_socket' do
expect(fog_options[:hardware][:cpu][:cores_per_socket]).to eq(2)
end
end

describe 'disks' do
it 'add' do
expect(fog_options[:hardware][:disk]).to include(:capacity_mb => 4096)
end

it 'resize' do
expect(fog_options[:hardware][:disk]).to include(:id => '2000', :capacity_mb => 6144)
end

it 'remove' do
expect(fog_options[:hardware][:disk]).to include(:id => '2001', :capacity_mb => -1)
end
end
end

context 'VM on' do
before { vm.raw_power_state = 'on' }

describe 'memory' do
it 'add memory' do
expect(fog_options[:hardware][:memory][:quantity_mb]).to eq(16_384)
end

it 'remove memory' do
options[:vm_memory] = 512
expect { fog_options }.to raise_error(MiqException::MiqVmError, 'Cannot remove memory from a running VM')
end
end

describe 'cpu' do
it 'add cores' do
expect(fog_options[:hardware][:cpu][:num_cores]).to eq(16)
end

it 'add cores per socket' do
expect(fog_options[:hardware][:cpu][:cores_per_socket]).to eq(2)
end

it 'remove cores per socket' do
options[:cores_per_socket] = 1
expect { fog_options }.to raise_error(MiqException::MiqVmError, 'Cannot change CPU cores per socket on a running VM')
end
end

describe 'hot memory disabled' do
before { vm.memory_hot_add_enabled = false }

it 'add memory' do
expect { fog_options }.to raise_error(MiqException::MiqVmError, 'Memory Hot-Add not enabled')
end
end

describe 'hot cpu add disabled' do
before { vm.cpu_hot_add_enabled = false }

it 'add cores' do
expect { fog_options }.to raise_error(MiqException::MiqVmError, 'CPU Hot-Add not enabled')
end
end

describe 'hot cpu remove disabled' do
before { vm.cpu_hot_remove_enabled = false }

it 'remove cores' do
options[:number_of_cpus] = 1
expect { fog_options }.to raise_error(MiqException::MiqVmError, 'CPU Hot-Remove not enabled')
end
end
end

context 'VM with snapshot' do
before { FactoryGirl.create(:snapshot_without_oncreate, :vm_or_template => vm) }

describe 'disks' do
it 'resize' do
expect { fog_options }.to raise_error(MiqException::MiqVmError, 'Cannot resize disk of VM with shapshots')
end
end
end
end
end
36 changes: 36 additions & 0 deletions spec/models/manageiq/providers/vmware/cloud_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,40 @@
end
end
end

describe 'reconfigure operations' do
before do
allow(@ems).to receive(:with_provider_connection).and_yield(connection)
end

let(:vm) { FactoryGirl.create(:vm_vcloud, :ext_management_system => @ems) }
let(:connection) { double('connection') }
let(:vm_xml) { double('vm_xml') }
let(:options) { { :spec => 'fog-options' } }

it 'supports reconfigure_disks' do
expect(vm.supports_reconfigure_disks?).to be_truthy
end

describe 'supports reconfigure_disksize' do
it 'without snapshots' do
expect(vm.supports_reconfigure_disksize?).to be_truthy
end

context 'with snapshots' do
before { FactoryGirl.create(:snapshot, :vm_or_template => vm) }

it do
expect(vm.supports_reconfigure_disksize?).to be_falsey
end
end
end

it '.vm_reconfigure' do
expect(connection).to receive(:get_vapp).with(vm.ems_ref, :parser => 'xml').and_return(double(:body => vm_xml))
expect(connection).to receive(:post_reconfigure_vm).with(vm.ems_ref, vm_xml, 'fog-options').and_return(double(:body => nil))
expect(connection).to receive(:process_task)
@ems.vm_reconfigure(vm, options)
end
end
end

0 comments on commit 47e6b79

Please sign in to comment.