# frozen_string_literal: true # # Author:: Chef Partner Engineering () # Copyright:: Copyright (c) 2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "ffi_yajl" shared_examples_for "a resource action" do |action_method, action_name| context "when the action is available" do it "calls gets the action ID and submits the request" do expect(resource).to receive(:action_id_by_name).with(action_name).and_return("action-123") expect(resource).to receive(:submit_action_request).with("action-123") resource.send(action_method) end end context "when the action is not available" do it "raises an exception" do expect(resource).to receive(:action_id_by_name).with(action_name).and_return nil expect { resource.send(action_method) }.to raise_error(Vra::Exception::NotFound) end end end describe Vra::Resource do let(:client) do Vra::Client.new(username: "user@corp.local", password: "password", tenant: "tenant", base_url: "https://vra.corp.local") end let(:resource_id) { "31a7badc-6562-458d-84f3-ec58d74a6953" } let(:vm_payload) do FFI_Yajl::Parser.parse(File.read(File.join(File.dirname(__FILE__), "fixtures", "resource", "vm_resource.json"))) end let(:vm_payload_no_ops) do FFI_Yajl::Parser.parse(File.read(File.join(File.dirname(__FILE__), "fixtures", "resource", "vm_resource_no_operations.json"))) end let(:non_vm_payload) do FFI_Yajl::Parser.parse(File.read(File.join(File.dirname(__FILE__), "fixtures", "resource", "non_vm_resource.json"))) end describe "#by_name" do it "can lookup and return resource" do resource = Vra::Resource.allocate allow(resource).to receive(:name).and_return("host1234") name = resource.name expect(Vra::Resources).to receive(:all).with(client).and_return([resource]) expect(Vra::Resource.by_name(client, name)).to eq(resource) end it "returns nil if nothing found" do resource = Vra::Resource.allocate allow(resource).to receive(:name).and_return("host1234") name = "somethingelse1234" expect(Vra::Resources).to receive(:all).with(client).and_return([resource]) expect(Vra::Resource.by_name(client, name)).to be nil end end describe "#initialize" do it "raises an error if no ID or resource data have been provided" do expect { Vra::Resource.new }.to raise_error(ArgumentError) end it "raises an error if an ID and resource data have both been provided" do expect { Vra::Resource.new(id: 123, data: "foo") }.to raise_error(ArgumentError) end context "when an ID is provided" do it "calls fetch_resource_data" do resource = Vra::Resource.allocate expect(resource).to receive(:fetch_resource_data) resource.send(:initialize, client, id: resource_id) end end context "when resource data is provided" do it "populates the ID correctly" do resource = Vra::Resource.new(client, data: vm_payload) expect(resource.id).to eq resource_id end end end describe "#fetch_resource_data" do it "calls get_parsed against the resources API endpoint" do expect(client).to receive(:get_parsed) .with("/catalog-service/api/consumer/resources/#{resource_id}") .and_return({}) Vra::Resource.new(client, id: resource_id) end end context "when a valid VM resource instance has been created" do let(:resource) { Vra::Resource.new(client, data: vm_payload) } describe "#name" do it "returns the correct name" do expect(resource.name).to eq "hol-dev-11" end end describe "#description" do it "returns the correct description" do expect(resource.description).to eq "test-description" end end describe "#status" do it "returns the correct status" do expect(resource.status).to eq "ACTIVE" end end describe "#vm?" do context "when the resource type is Infrastructure.Virtual" do let(:resource_data) { { "resourceTypeRef" => { "id" => "Infrastructure.Virtual" } } } it "returns true" do allow(resource).to receive(:resource_data).and_return(resource_data) expect(resource.vm?).to eq(true) end end context "when the resource type is Infrastructure.Cloud" do let(:resource_data) { { "resourceTypeRef" => { "id" => "Infrastructure.Cloud" } } } it "returns true" do allow(resource).to receive(:resource_data).and_return(resource_data) expect(resource.vm?).to eq(true) end end context "when the resource type is an unknown type" do let(:resource_data) { { "resourceTypeRef" => { "id" => "Infrastructure.Unknown" } } } it "returns false" do allow(resource).to receive(:resource_data).and_return(resource_data) expect(resource.vm?).to eq(false) end end end describe "#tenant_id" do it "returns the correct tenant ID" do expect(resource.tenant_id).to eq "vsphere.local" end end describe "#tenant_name" do it "returns the correct tenant name" do expect(resource.tenant_name).to eq "vsphere.local" end end describe "#subtenant_id" do it "returns the correct subtenant ID" do expect(resource.subtenant_id).to eq "5327ddd3-1a4e-4663-9e9d-63db86ffc8af" end end describe "#subtenant_name" do it "returns the correct subtenant name" do expect(resource.subtenant_name).to eq "Rainpole Developers" end end describe "#owner_ids" do it "returns the correct owner IDs" do expect(resource.owner_ids).to eq %w{user1@corp.local user2@corp.local} end end describe "#owner_names" do it "returns the correct owner names" do expect(resource.owner_names).to eq [ "Joe User", "Jane User" ] end end describe "#machine_status" do context "when no MachineStatus exists" do let(:resource_data) { { "resourceData" => { "entries" => [] } } } it "raises an exception" do allow(resource).to receive(:resource_data).and_return(resource_data) expect { resource.machine_status }.to raise_error(RuntimeError) end end context "when MachineStatus Exists" do let(:resource_data) do { "resourceData" => { "entries" => [ { "key" => "MachineStatus", "value" => { "type" => "string", "value" => "Off" }, }, ], }, } end it "returns the correct status value" do allow(resource).to receive(:resource_data).and_return(resource_data) expect(resource.machine_status).to eq("Off") end end end describe "#machine_on?" do it "returns true if the machine_status is On" do allow(resource).to receive(:machine_status).and_return("On") expect(resource.machine_on?).to eq(true) end it "returns false if the machine_status is not On" do allow(resource).to receive(:machine_status).and_return("Off") expect(resource.machine_on?).to eq(false) end end describe "#machine_off?" do it "returns true if the machine_status is Off" do allow(resource).to receive(:machine_status).and_return("Off") expect(resource.machine_off?).to eq(true) end it "returns false if the machine_status is not Off" do allow(resource).to receive(:machine_status).and_return("On") expect(resource.machine_off?).to eq(false) end end describe "#machine_turning_on?" do it "returns true if the machine_status is TurningOn" do allow(resource).to receive(:machine_status).and_return("TurningOn") expect(resource.machine_turning_on?).to eq(true) end it "returns true if the machine_status is MachineActivated" do allow(resource).to receive(:machine_status).and_return("MachineActivated") expect(resource.machine_turning_on?).to eq(true) end it "returns false if the machine_status is not TurningOn" do allow(resource).to receive(:machine_status).and_return("On") expect(resource.machine_turning_on?).to eq(false) end end describe "#machine_turning_off?" do it "returns true if the machine_status is TurningOff" do allow(resource).to receive(:machine_status).and_return("TurningOff") expect(resource.machine_turning_off?).to eq(true) end it "returns true if the machine_status is ShuttingDown" do allow(resource).to receive(:machine_status).and_return("ShuttingDown") expect(resource.machine_turning_off?).to eq(true) end it "returns false if the machine_status is not TurningOff or ShuttingDown" do allow(resource).to receive(:machine_status).and_return("Off") expect(resource.machine_turning_off?).to eq(false) end end describe "#machine_in_provisioned_state?" do it "returns true if the machine_status is MachineProvisioned" do allow(resource).to receive(:machine_status).and_return("MachineProvisioned") expect(resource.machine_in_provisioned_state?).to eq(true) end it "returns false if the machine_status is not MachineProvisioned" do allow(resource).to receive(:machine_status).and_return("On") expect(resource.machine_in_provisioned_state?).to eq(false) end end describe "#network_interfaces" do it "returns an array of 2 elements" do expect(resource.network_interfaces.size).to be 2 end it "contains the correct data" do nic1, nic2 = resource.network_interfaces expect(nic1["NETWORK_NAME"]).to eq "VM Network" expect(nic1["NETWORK_ADDRESS"]).to eq "192.168.110.200" expect(nic1["NETWORK_MAC_ADDRESS"]).to eq "00:50:56:ae:95:3c" expect(nic2["NETWORK_NAME"]).to eq "Management Network" expect(nic2["NETWORK_ADDRESS"]).to eq "192.168.220.200" expect(nic2["NETWORK_MAC_ADDRESS"]).to eq "00:50:56:ae:95:3d" end end describe "#ip_addresses" do let(:authn_payload) do { "username" => "user@corp.local", "password" => "password", "tenant" => "tenant", }.to_json end let(:resource_view_body) do File.read(File.join(File.dirname(__FILE__), "fixtures", "resource", "ip_address.txt")) end before do stub_request( :post, "https://vra.corp.local/identity/api/tokens" ).with( :body => authn_payload, :headers => { "Accept" => "application/json", "Content-Type" => "application/json" } ).to_return( :status => 200, :body => '{"id":"12345"}', :headers => {} ) stub_request( :head, "https://vra.corp.local/identity/api/tokens/12345" ).with( :headers => { "Accept" => "application/json", "Authorization" => "Bearer 12345", "Content-Type" => "application/json" } ).to_return( :status => 204, :body => "", :headers => {} ) stub_request( :get, "https://vra.corp.local/catalog-service/api/consumer/requests/bogus/resourceViews" ).with( headers: { "Accept" => "application/json", "Content-Type" => "application/json" } ).to_return( status: 200, body: resource_view_body, headers: {} ) end context "with IP address in the second element" do it "returns the correct IP addresses" do expect(resource.ip_addresses).to eq [ "172.16.20.147" ] end end context "with IP address in the third element" do let(:resource_view_body) do File.read(File.join(File.dirname(__FILE__), "fixtures", "resource", "ip_address_third_element.txt")) end it "returns the correct IP addresses" do expect(resource.ip_addresses).to eq [ "172.16.20.147" ] end end it "returns nil if there are no network interfaces" do allow(resource).to receive(:network_interfaces).and_return nil expect(resource.ip_addresses).to be_nil end end describe "#actions" do it "does not call #fetch_resource_data" do expect(resource).not_to receive(:fetch_resource_data) resource.actions end end describe "#action_id_by_name" do it "returns the correct action ID for the destroy action" do expect(resource.action_id_by_name("Destroy")).to eq "ace8ba42-e724-48d8-9614-9b3a62b5a464" end it "returns nil if there are no resource operations" do allow(resource).to receive(:actions).and_return nil expect(resource.action_id_by_name("Destroy")).to be_nil end it "returns nil if there are actions, but none with the right name" do allow(resource).to receive(:actions).and_return([ { "name" => "some action" }, { "name" => "another action" } ]) expect(resource.action_id_by_name("Destroy")).to be_nil end end describe "#destroy" do it_behaves_like "a resource action", :destroy, "Destroy" end describe "#shutdown" do it_behaves_like "a resource action", :shutdown, "Shutdown" end describe "#poweroff" do it_behaves_like "a resource action", :poweroff, "Power Off" end describe "#poweron" do it_behaves_like "a resource action", :poweron, "Power On" end describe "#submit_action_request" do before do allow(resource).to receive(:action_request_payload).and_return({}) response = double("response", location: "/requests/request-12345") allow(client).to receive(:http_post).with("/catalog-service/api/consumer/requests", "{}").and_return(response) end it "calls http_post" do expect(client).to receive(:http_post).with("/catalog-service/api/consumer/requests", "{}") resource.submit_action_request("action-123") end it "returns a Vra::Request object" do expect(resource.submit_action_request("action-123")).to be_an_instance_of(Vra::Request) end end end context "when a valid VM resource instance with no operations is created" do let(:resource) { Vra::Resource.new(client, data: vm_payload_no_ops) } describe "#actions" do it "calls #fetch_resource_data" do expect(resource).to receive(:fetch_resource_data) resource.actions end end end context "when a valid non-VM resource instance has been created" do let(:resource) { Vra::Resource.new(client, data: non_vm_payload) } it "returns nil for network_interfaces and ip_addresses" do expect(resource.network_interfaces).to be_nil expect(resource.ip_addresses).to be_nil end end end