spec/kitchen/driver/linode_spec.rb in kitchen-linode-0.14.0 vs spec/kitchen/driver/linode_spec.rb in kitchen-linode-0.15.0

- old
+ new

@@ -1,172 +1,336 @@ -# Encoding: UTF-8 +require_relative "../../spec_helper" +require_relative "../../../lib/kitchen/driver/linode" -require_relative '../../spec_helper' -require_relative '../../../lib/kitchen/driver/linode' +require "logger" +require "stringio" +require "rspec" +require "kitchen" +require "kitchen/driver/linode" +require "kitchen/provisioner/dummy" +require "kitchen/transport/dummy" +require "kitchen/verifier/dummy" +require "fog/linode" -require 'logger' -require 'stringio' -require 'rspec' -require 'kitchen' -require 'kitchen/driver/linode' -require 'kitchen/provisioner/dummy' -require 'kitchen/transport/dummy' -require 'kitchen/verifier/dummy' -require 'fog' - describe Kitchen::Driver::Linode do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } - let(:config) { Hash.new } - let(:state) { Hash.new } - let(:rsa) { File.expand_path('~/.ssh/id_rsa') } - let(:instance_name) { 'the_thing' } + let(:config) { {} } + let(:state) { {} } + let(:rsa) { File.expand_path("~/.ssh/id_rsa") } + let(:uuid_password) { "397a60bf-c7ac-4f5a-90c8-994fd835af8f" } + let(:instance_name) { "kitchen-test" } let(:transport) { Kitchen::Transport::Dummy.new } - let(:platform) { Kitchen::Platform.new(name: 'fake_platform') } + let(:platform) { Kitchen::Platform.new(name: "linode/test") } let(:driver) { Kitchen::Driver::Linode.new(config) } let(:instance) do double( name: instance_name, transport: transport, logger: logger, platform: platform, - to_str: 'instance' + to_str: "instance" ) end let(:driver) { described_class.new(config) } before(:each) do allow_any_instance_of(described_class).to receive(:instance) .and_return(instance) allow(File).to receive(:exist?).and_call_original allow(File).to receive(:exist?).with(rsa).and_return(true) + allow(SecureRandom).to receive(:uuid).and_return(uuid_password) + # skip sleeping so we're not waiting + Retryable.configure do |config| + config.sleep_method = lambda { |n| nil } + end + allow(driver).to receive(:sleep).and_return(nil) end - - describe '#finalize_config' do + + describe "#finalize_config" do before(:each) { allow(File).to receive(:exist?).and_return(false) } - context 'private key, public key, and api key provided' do + context "private key, public key, and api token provided" do let(:config) do - { private_key_path: '/tmp/key', - public_key_path: '/tmp/key.pub', - api_key: 'mykey' } + { private_key_path: "/tmp/key", + public_key_path: "/tmp/key.pub", + linode_token: "mytoken" } end - it 'raises no error' do + it "raises no error" do expect(driver.finalize_config!(instance)).to be end end end - describe '#initialize' do - context 'default options' do - context 'only a RSA SSH key available for the user' do + describe "#initialize" do + context "default options" do + context "only a RSA SSH key available for the user" do before(:each) do allow(File).to receive(:exist?).and_return(false) allow(File).to receive(:exist?).with(rsa).and_return(true) + allow(File).to receive(:exist?).with(rsa + ".pub").and_return(true) end - it 'uses the local user\'s RSA private key' do + it "uses the local user's RSA private key" do expect(driver[:private_key_path]).to eq(rsa) end - it 'uses the local user\'s RSA public key' do - expect(driver[:public_key_path]).to eq(rsa + '.pub') + it "uses the local user's RSA public key" do + expect(driver[:public_key_path]).to eq(rsa + ".pub") end end - - nils = [ - :server_name, - :password - ] - nils.each do |i| - it "defaults to no #{i}" do - expect(driver[i]).to eq(nil) - end + + it "defaults to no label" do + expect(driver[:label]).to eq(nil) end - - end - context 'overridden options' do - let(:config) do - { - image: 139, - data_center: 10, - flavor: 2, - kernel: 215, - username: 'someuser', - server_name: 'thisserver', - private_key_path: '/path/to/id_rsa', - public_key_path: '/path/to/id_rsa.pub', - password: 'somepassword' - } + + it "defaults to a UUID as the password" do + expect(driver[:password]).to eq(uuid_password) end - it 'uses all the overridden options' do - drv = driver - config.each do |k, v| - expect(drv[k]).to eq(v) + end + context "overridden options" do + config = { + linode_token: "mytesttoken", + password: "somepassword", + label: "thisserver", + tags: %w{kitchen deleteme}, + hostname: "clevername", + image: "linode/ubuntu20.04", + region: "eu-central", + type: "g6-standard-2", + stackscript_id: 12345, + stackscript_data: { test: "1234" }, + swap_size: 256, + private_ip: true, + authorized_users: ["timmy"], + private_key_path: "/path/to/id_rsa", + public_key_path: "/path/to/id_rsa.pub", + disable_ssh_password: false, + api_retries: 2, + } + + let(:config) { config } + + config.each do |key, value| + it "it uses the overridden #{key} option" do + expect(driver[key]).to eq(value) end end - - it 'overrides server name prefix with explicit server name, if given' do - expect(driver[:server_name]).to eq(config[:server_name]) - end end end - - describe '#create' do - let(:server) do - double(id: 'test123', wait_for: true, public_ip_address: %w(1.2.3.4)) - end + + describe "#create" do + let(:linode_label) { "kitchen-test_500" } let(:driver) do d = super() - allow(d).to receive(:create_server).and_return(server) - allow(d).to receive(:do_ssh_setup).and_return(true) + allow(d).to receive(:setup_server).and_return(nil) d end - context 'when a server is already created' do - it 'does not create a new instance' do - state[:server_id] = '1' + context "when a server is already created" do + it "does not create a new instance" do + state[:linode_id] = "1" expect(driver).not_to receive(:create_server) driver.create(state) end end - context 'required options provided' do - let(:config) do + context "required options provided" do + let(:driver) do + d = super() + allow(d).to receive(:setup_server).and_return(nil) + allow(d).to receive(:suffixes).and_return((500..505)) + d + end + let(:config) { { - username: 'someuser', - api_key: 'somekey', - disable_ssl_validation: false + linode_token: "somekey", } + } + before(:each) do + ENV["JOB_NAME"] = nil + ENV["GITHUB_JOB"] = nil end - let(:server) do - double(id: 'test123', wait_for: true, public_ip_address: %w(1.2.3.4)) + + it "returns nil, but modifies the state" do + post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances") + .to_return(lambda { |request| create_response(request) }) + list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances") + .to_return(list_response) + get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357") + .to_return(view_response(linode_label, "us-east", "linode/test", "g6-nanode-1")) + expect(driver.send(:create, state)).to eq(nil) + expect(post_stub).to have_been_made.times(1) + expect(list_stub).to have_been_made.times(1) + expect(get_stub).to have_been_made.times(1) + expect(state[:linode_id]).to eq(73577357) + expect(state[:linode_label]).to eq("kitchen-job-kitchen-test_500") end - let(:driver) do - d = described_class.new(config) - allow(d).to receive(:create_server).and_return(server) - allow(server).to receive(:id).and_return('test123') + it "handles rate limits and connection timeouts like a champ" do + post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances") + .to_return( + create_timeout_response, + create_timeout_response, + create_ratelimit_response, + create_ratelimit_response, + lambda { |request| create_response(request) } + ) + list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances") + .to_return(list_response) + get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357") + .to_return(view_response(linode_label, "us-east", "linode/test", "g6-nanode-1")) + driver.send(:create, state) + expect(post_stub).to have_been_made.times(5) + expect(list_stub).to have_been_made.times(1) + expect(get_stub).to have_been_made.times(1) + expect(state[:linode_id]).to eq(73577357) + expect(state[:linode_label]).to eq("kitchen-job-kitchen-test_500") + end - allow(server).to receive(:wait_for) - .with(an_instance_of(Fixnum)).and_yield - allow(d).to receive(:bourne_shell?).and_return(false) - d + it "raises an error if we run out of retries" do + allow(driver).to receive(:sleep).and_return(nil) # skip sleeping so we're not waiting + post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances") + .to_return( + create_timeout_response, + create_timeout_response, + create_timeout_response, + create_timeout_response, + create_timeout_response + ) + list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances") + .to_return(list_response) + expect { driver.send(:create, state) }.to raise_error(Kitchen::ActionFailed) + expect(list_stub).to have_been_made.times(1) + expect(post_stub).to have_been_made.times(5) end - it 'returns nil, but modifies the state' do - expect(driver.send(:create, state)).to eq(nil) - expect(state[:server_id]).to eq('test123') + it "raises an error if the api says we provided garbage data" do + allow(driver).to receive(:sleep).and_return(nil) # skip sleeping so we're not waiting + post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances") + .to_return(create_bad_response) + list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances") + .to_return(list_response) + expect { driver.send(:create, state) }.to raise_error(Kitchen::UserError) + expect(list_stub).to have_been_made.times(1) + expect(post_stub).to have_been_made.times(1) end - it 'throws an Action error when trying to create_server' do + it "it picks a different suffix when other servers exist" do + post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances") + .to_return(lambda { |request| create_response(request) }) + list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances") + .to_return( + body: '{"data": [{"label": "kitchen-job-kitchen-test_500"}, {"label": "kitchen-job-kitchen-test_501"}], "page": 1, "pages": 1, "results": 2}', + headers: { "Content-Type" => "application/json" } + ) + get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357") + .to_return(view_response("kitchen-job-kitchen-test_502", "us-east", "linode/test", "g6-nanode-1")) + driver.send(:create, state) + expect(post_stub).to have_been_made.times(1) + expect(list_stub).to have_been_made.times(1) + expect(get_stub).to have_been_made.times(1) + expect(state[:linode_label]).to eq("kitchen-job-kitchen-test_502") + end + + it "throws an Action error when trying to create_server" do allow(driver).to receive(:create_server).and_raise(Fog::Errors::Error) expect { driver.send(:create, state) }.to raise_error(Kitchen::ActionFailed) end end + + context "when all the label suffixes are taken" do + let(:compute) { + double( + servers: double( + all: double( + find: true + ) + ) + ) + } + before(:each) do + { + compute: compute, + }.each do |k, v| + allow_any_instance_of(described_class).to receive(k).and_return(v) + end + end + + it "throws a UserError" do + expect { driver.send(:create, state) }.to raise_error(Kitchen::UserError) + end + end + end -end \ No newline at end of file + describe "#destroy" do + let(:linode_id) { "73577357" } + let(:linode_label) { "kitchen-test_500" } + let(:hostname) { "203.0.113.243" } + let(:state) { + { + linode_id: linode_id, + linode_label: linode_label, + hostname: hostname, + } + } + let(:config) { + { + linode_token: "somekey", + } + } + let(:driver) { described_class.new(config) } + + context "when a server hasn't been created" do + it "does not destroy anything" do + state = {} + expect(driver).not_to receive(:compute) + expect(state).not_to receive(:delete) + driver.destroy(state) + expect(a_request(:get, "https://api.linode.com/v4/linode/instances/73577357")) + .not_to have_been_made + expect(a_request(:delete, "https://api.linode.com/v4/linode/instances/73577357")) + .not_to have_been_made + end + end + + context "when a server doesn't exist" do + it "doesn't get nervous about the 404" do + get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357") + .to_return(status: [404, "Not Found"]) + expect(state).to receive(:delete).with(:linode_id) + expect(state).to receive(:delete).with(:linode_label) + expect(state).to receive(:delete).with(:hostname) + expect(state).to receive(:delete).with(:ssh_key) + expect(state).to receive(:delete).with(:password) + driver.destroy(state) + expect(get_stub).to have_been_made.times(1) + expect(a_request(:delete, "https://api.linode.com/v4/linode/instances/73577357")) + .not_to have_been_made + end + end + + context "when a server exists" do + it "properly nukes it" do + get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357") + .to_return(view_response(linode_label, "us-test", "linode/test", "testnode")) + delete_stub = stub_request(:delete, "https://api.linode.com/v4/linode/instances/73577357") + .to_return(delete_response) + expect(state).to receive(:delete).with(:linode_id) + expect(state).to receive(:delete).with(:linode_label) + expect(state).to receive(:delete).with(:hostname) + expect(state).to receive(:delete).with(:ssh_key) + expect(state).to receive(:delete).with(:password) + driver.destroy(state) + expect(get_stub).to have_been_made.times(1) + expect(delete_stub).to have_been_made.times(1) + end + end + + end + +end