require 'spec_helper' describe Vcloud::Core::Vm do before(:all) do config_file = File.join(File.dirname(__FILE__), "../vcloud_tools_testing_config.yaml") required_user_params = [ "catalog", "default_storage_profile_name", "network_1", "network_1_ip", "network_2", "network_2_ip", "storage_profile", "vapp_template", "vdc_1_name", ] @test_params = Vcloud::Tools::Tester::TestSetup.new(config_file, required_user_params).test_params @network_names = [ @test_params.network_1, @test_params.network_2 ] @network_ips = { @test_params.network_1 => @test_params.network_1_ip, @test_params.network_2 => @test_params.network_2_ip, } @test_case_vapps = IntegrationHelper.create_test_case_vapps( 1, @test_params.vdc_1_name, @test_params.catalog, @test_params.vapp_template, @network_names, "vcloud-core-vm-tests" ) @test_case_independent_disk_list = IntegrationHelper.create_test_case_independent_disks( 1, @test_params.vdc_1_name, "100MB", "vcloud-core-vm-test-disk" ) @vapp = @test_case_vapps.first vapp_vms = @vapp.vms.map do |vm| vm_id = vm[:href].split('/').last Vcloud::Core::Vm.new(vm_id, @vapp) end @vm = vapp_vms.first end context "#vcloud_attributes" do it "has a :href element containing the expected VM id" do expect(@vm.vcloud_attributes[:href].split('/').last).to eq(@vm.id) end end context "#update_memory_size_in_mb" do it "can increase the memory size by 512MB" do initial_memory_size = Integer(@vm.memory) # Vm#memory returns a string memory_to_add_in_mb = 512 new_memory_size = initial_memory_size + memory_to_add_in_mb @vm.update_memory_size_in_mb(new_memory_size) expect(Integer(@vm.memory)).to eq(new_memory_size) end it "can reduce the memory size by 512MB" do initial_memory_size = Integer(@vm.memory) # Vm#memory returns a string memory_to_remove_in_mb = 512 new_memory_size = initial_memory_size - memory_to_remove_in_mb @vm.update_memory_size_in_mb(new_memory_size) expect(Integer(@vm.memory)).to eq(new_memory_size) end end context "#update_name" do it "can update the name of the vm" do current_name = @vm.name new_name = "#{current_name}-updated" @vm.update_name(new_name) expect(@vm.name).to eq(new_name) end end context "#vapp_name" do it "can retrieve the name of its parent vApp" do expect(@vm.vapp_name).to eq(@vapp.name) end end context "#update_cpu_count" do it "can increase the number of CPUs in a VM" do initial_cpu_count = Integer(@vm.cpu) # Vm#cpu returns a string :( new_cpu_count = initial_cpu_count * 2 @vm.update_cpu_count(new_cpu_count) expect(Integer(@vm.cpu)).to eq(new_cpu_count) end it "can decrease the number of CPUs in a VM to 1" do initial_cpu_count = Integer(@vm.cpu) # Vm#cpu returns a string :( new_cpu_count = 1 expect(initial_cpu_count).to be > new_cpu_count @vm.update_cpu_count(new_cpu_count) expect(Integer(@vm.cpu)).to eq(new_cpu_count) end end context "#update_metadata" do before(:all) do @initial_vm_metadata = Vcloud::Core::Vm.get_metadata(@vm.id) @initial_vapp_metadata = Vcloud::Core::Vapp.get_metadata(@vapp.id) end it "updates the Vm metadata, if a single key/value is specified" do @vm.update_metadata({"Test Key" => "test value"}) updated_metadata = Vcloud::Core::Vm.get_metadata(@vm.id) # get_metadata is symbolizing the key names expected_metadata = @initial_vm_metadata.merge({ :"Test Key" => "test value" }) expect(updated_metadata).to eq(expected_metadata) end it "adds to the existing Vm metadata, rather than replacing it" do @vm.update_metadata({"Another Test" => "test value 2"}) updated_metadata = Vcloud::Core::Vm.get_metadata(@vm.id) # get_metadata is symbolizing the key names expected_metadata = @initial_vm_metadata.merge({ :"Test Key" => "test value", :"Another Test" => "test value 2", }) expect(updated_metadata).to eq(expected_metadata) end it "has also updated parent vApp with the same metadata" do updated_vapp_metadata = Vcloud::Core::Vapp.get_metadata(@vapp.id) # get_metadata is symbolizing the key names expected_vapp_metadata = @initial_vapp_metadata.merge({ :"Test Key" => "test value", :"Another Test" => "test value 2", }) expect(updated_vapp_metadata).to eq(expected_vapp_metadata) end end context "#add_extra_disks" do before(:all) do @fog_model_vm = Vcloud::Core::Fog::ModelInterface.new.get_vm_by_href(@vm.href) @initial_vm_disks = get_vm_hard_disks(@fog_model_vm) end it "the VM should already have a single disk assigned" do expect(@initial_vm_disks.size).to eq(1) end it "can successfully add a second disk" do extra_disks = [ { size: '20480' } ] @vm.add_extra_disks(extra_disks) updated_vm_disks = get_vm_hard_disks(@fog_model_vm) expect(updated_vm_disks.size).to eq(2) end it "can successfully add several disks in one call" do extra_disks = [ { size: '20480' }, { size: '10240' } ] disks_before_update = get_vm_hard_disks(@fog_model_vm) @vm.add_extra_disks(extra_disks) disks_after_update = get_vm_hard_disks(@fog_model_vm) expect(disks_after_update.size).to eq(disks_before_update.size + extra_disks.size) end end context "#configure_network_interfaces" do it "can configure a single NIC, default DHCP" do network_config = [ { :name => @network_names[0] } ] @vm.configure_network_interfaces(network_config) # if number if nics is 1, API returns a Hash. # This is a bug in Fog -- ensure_list! is needed. See # https://github.com/fog/fog/issues/2927 vm_nics = @vm.vcloud_attributes[:NetworkConnectionSection][:NetworkConnection] expect(vm_nics).to be_instance_of(Hash) expect(vm_nics[:network]).to eq(network_config[0][:name]) expect(vm_nics[:IpAddressAllocationMode]).to eq('DHCP') end it "can configure dual NICs, both defaulting to DHCP" do network_config = [ { :name => @network_names[0] }, { :name => @network_names[1] } ] @vm.configure_network_interfaces(network_config) # if number if nics is 2+, API returns a Array. # See https://github.com/fog/fog/issues/2927 vm_nics = @vm.vcloud_attributes[:NetworkConnectionSection][:NetworkConnection] vm_nics = sort_nics_based_on_network_connection_index(vm_nics) expect(vm_nics).to be_instance_of(Array) expect(vm_nics.size).to eq(network_config.size) expect(vm_nics[0][:network]).to eq(network_config[0][:name]) expect(vm_nics[0][:IpAddressAllocationMode]).to eq('DHCP') expect(vm_nics[0][:NetworkConnectionIndex]).to eq('0') expect(vm_nics[1][:network]).to eq(network_config[1][:name]) expect(vm_nics[1][:IpAddressAllocationMode]).to eq('DHCP') expect(vm_nics[1][:NetworkConnectionIndex]).to eq('1') end it "can configure dual NICs with manually assigned IP addresses" do network_config = [ { :name => @network_names[0], :ip_address => @network_ips[@network_names[0]] }, { :name => @network_names[1], :ip_address => @network_ips[@network_names[1]] }, ] @vm.configure_network_interfaces(network_config) # if number if nics is 2+, API returns a Array. vm_nics = @vm.vcloud_attributes[:NetworkConnectionSection][:NetworkConnection] vm_nics = sort_nics_based_on_network_connection_index(vm_nics) expect(vm_nics).to be_instance_of(Array) expect(vm_nics.size).to eq(network_config.size) expect(vm_nics[0][:network]).to eq(network_config[0][:name]) expect(vm_nics[0][:IpAddress]).to eq(network_config[0][:ip_address]) expect(vm_nics[0][:IpAddressAllocationMode]).to eq('MANUAL') expect(vm_nics[0][:NetworkConnectionIndex]).to eq('0') expect(vm_nics[1][:network]).to eq(network_config[1][:name]) expect(vm_nics[1][:IpAddress]).to eq(network_config[1][:ip_address]) expect(vm_nics[1][:IpAddressAllocationMode]).to eq('MANUAL') expect(vm_nics[1][:NetworkConnectionIndex]).to eq('1') end end context "#attach_independent_disks" do it "can attach our fixture disk" do disk = @test_case_independent_disk_list.first expect(disk.attached_vms).to be_empty @vm.attach_independent_disks(@test_case_independent_disk_list) expect(disk.attached_vms.first.id).to eq(@vm.id) end end # NB: It is suspected that this behaviour is caused by the Fog Model, not # vCloud Director itself. Issue raised on fog/fog to investigate/fix: # https://github.com/fog/fog/issues/3179 context "local disks cannot be added whilst an independent disk is attached" do it "raises an error if we now try to add an extra local disk" do extra_disks = [ { size: '10240' } ] expect { @vm.add_extra_disks(extra_disks) }. to raise_error( Fog::Compute::VcloudDirector::BadRequest, "The attached disks on VM \"#{@vm.name}\" cannot be modified." ) end end describe "cannot update the storage profile of a VM with an independent disk attached" do context "#update_storage_profile" do it "throws an error when trying to update the storage profile of a VM with an " + "independent disk attached" do available_storage_profiles = Vcloud::Core::QueryRunner.new.run( 'orgVdcStorageProfile', filter: "vdcName==#{@test_params.vdc_1_name}" ) if available_storage_profiles.size == 1 pending("There is only one StorageProfile in vDC #{@test_params.vdc_1_name}: cannot test.") end expect{ @vm.update_storage_profile(@test_params.storage_profile) }. to raise_error(Fog::Compute::VcloudDirector::TaskError) end end end describe "detach the independent disk causing the storage profile problem" do context "#detach_independent_disks" do it "can detach independent disks from the VM" do disk = @test_case_independent_disk_list.first expect(disk.attached_vms.first.id).to eq(@vm.id) @vm.detach_independent_disks(@test_case_independent_disk_list) expect(disk.attached_vms).to be_empty end end end describe "can now update the storage profile" do context "#update_storage_profile" do it "can update the storage profile of a VM" do available_storage_profiles = Vcloud::Core::QueryRunner.new.run( 'orgVdcStorageProfile', filter: "vdcName==#{@test_params.vdc_1_name}" ) if available_storage_profiles.size == 1 pending("There is only one StorageProfile in vDC #{@test_params.vdc_1_name}: cannot test.") end original_storage_profile_name = @vm.vcloud_attributes[:StorageProfile][:name] expect(original_storage_profile_name).to eq(@test_params.default_storage_profile_name) @vm.update_storage_profile(@test_params.storage_profile) expect(@vm.vcloud_attributes[:StorageProfile][:name]).to eq(@test_params.storage_profile) end end end after(:all) do IntegrationHelper.delete_vapps(@test_case_vapps) IntegrationHelper.delete_independent_disks(@test_case_independent_disk_list) end def get_vm_hard_disks(fog_model_vm) # 'disks' Model VM method returns disks + controllers. Disks always have # the name 'Hard Disk {n}' where (n >= 0). fog_model_vm.disks.select { |disk| disk.name =~ /^Hard disk/ } end def sort_nics_based_on_network_connection_index(network_connection_list) # The :NetworkConnection Array is not (necessarily) ordered when it is # retrieved via the API. # Instead, they are indexed by the :NetworkConnectionIndex value, which # is returned by the API as a number-as-a-string (eg "0", "1") network_connection_list.sort_by do |n| Integer(n[:NetworkConnectionIndex]) end end end