require 'spec_helper' module Beaker describe AwsSdk do let( :options ) { make_opts.merge({ 'logger' => double().as_null_object, 'timestamp' => Time.now }) } let(:aws) { # Mock out the call to load_fog_credentials allow_any_instance_of( Beaker::AwsSdk ). to receive(:load_fog_credentials). and_return(Aws::Credentials.new( fog_file_contents[:default][:aws_access_key_id], fog_file_contents[:default][:aws_secret_access_key], fog_file_contents[:default][:aws_session_token], )) # This is needed because the EC2 api looks up a local endpoints.json file FakeFS.deactivate! aws = Beaker::AwsSdk.new(@hosts, options) aws_partitions_dir = Gem::Specification.find_by_name('aws-partitions').gem_dir FakeFS.activate! allow(File).to receive(:exist?).with(File.join(aws_partitions_dir, 'partitions.json')) FakeFS::FileSystem.clone(File.join(aws_partitions_dir, 'partitions.json')) aws } let(:amispec) {{ "centos-5-x86-64-west" => { :image => {:pe => "ami-sekrit1"}, :region => "us-west-2", }, "centos-6-x86-64-west" => { :image => {:pe => "ami-sekrit2"}, :region => "us-west-2", }, "centos-7-x86-64-west" => { :image => {:pe => "ami-sekrit3"}, :region => "us-west-2", }, "ubuntu-12.04-amd64-west" => { :image => {:pe => "ami-sekrit4"}, :region => "us-west-2" }, }} before :each do @hosts = make_hosts({:snapshot => :pe}, 7) @hosts[0][:platform] = "centos-5-x86-64-west" @hosts[1][:platform] = "centos-6-x86-64-west" @hosts[2][:platform] = "centos-7-x86-64-west" @hosts[3][:platform] = "ubuntu-12.04-amd64-west" @hosts[3][:user] = "ubuntu" @hosts[4][:platform] = 'f5-host' @hosts[4][:user] = 'notroot' @hosts[5][:platform] = 'netscaler-host' @hosts[6][:platform] = 'windows-host' ENV['AWS_ACCESS_KEY'] = nil ENV['AWS_SECRET_ACCESS_KEY'] = nil end context 'loading credentials' do it 'from .fog file' do creds = aws.load_fog_credentials expect(creds).to have_attributes( :access_key_id => 'IMANACCESSKEY', :secret_access_key => 'supersekritkey', :session_token =>'somecrazylongsupersessiontoken!#%^^*(%$^&@$%#!!#$asd;fjapugfrejklvznb;jdgfjiadvij', ) end it 'from environment variables' do ENV['AWS_ACCESS_KEY_ID'] = "IMANACCESSKEY" ENV['AWS_SECRET_ACCESS_KEY'] = "supersekritkey" creds = aws.load_env_credentials expect(creds).to have_attributes( :access_key_id => "IMANACCESSKEY", :secret_access_key => "supersekritkey", :session_token => nil, ) end it 'from environment variables with session_token' do ENV['AWS_ACCESS_KEY_ID'] = "IMANACCESSKEY" ENV['AWS_SECRET_ACCESS_KEY'] = "supersekritkey" ENV['AWS_SESSION_TOKEN'] = 'somesuperlongsessiontokenspecialcharsblah!#%$#@$^!@qewpofudjsvjm' creds = aws.load_env_credentials expect(creds).to have_attributes( :access_key_id => "IMANACCESSKEY", :secret_access_key => "supersekritkey", :session_token => 'somesuperlongsessiontokenspecialcharsblah!#%$#@$^!@qewpofudjsvjm', ) end end context 'dont read fog credentials' do let(:options) { make_opts.merge({ 'use_fog_credentials' => false }) } before(:each) do ENV.delete('AWS_ACCESS_KEY_ID') end it 'not using fog' do creds = aws.load_env_credentials expect(creds).to have_attributes( :access_key_id => nil, :secret_access_key => nil, ) expect(options[:use_fog_credentials]).to eq(false) end end describe '#provision' do before :each do expect(aws).to receive(:launch_all_nodes) expect(aws).to receive(:add_tags) expect(aws).to receive(:populate_dns) expect(aws).to receive(:enable_root_on_hosts) expect(aws).to receive(:set_hostnames) expect(aws).to receive(:configure_hosts) end it 'should step through provisioning' do allow( aws ).to receive( :wait_for_status_netdev ) aws.provision end it 'should return nil' do allow( aws ).to receive( :wait_for_status_netdev ) expect(aws.provision).to be_nil end end describe '#kill_instances' do def mock_instance(id, state) instance_double( Aws::EC2::Types::Instance, :state => instance_double(Aws::EC2::Types::InstanceState, :name => state), :instance_id => id, ) end let(:ec2_instance) { mock_instance('ec2', 'running') } let(:vpc_instance) { mock_instance('vpc', 'running') } let(:nil_instance) { nil } let(:unreal_instance) { mock_instance('unreal', 'terminated') } subject(:kill_instances) { aws.kill_instances(instance_set) } let(:mock_client) { instance_double(Aws::EC2::Client, :terminate_instances => nil) } before(:each) do allow(aws).to receive(:client).and_return(mock_client) allow(aws).to receive(:instance_by_id).with('ec2').and_return(ec2_instance) allow(aws).to receive(:instance_by_id).with('vpc').and_return(vpc_instance) allow(aws).to receive(:instance_by_id).with('nil').and_return(nil_instance) allow(aws).to receive(:instance_by_id).with('unreal').and_return(unreal_instance) end it 'should return nil' do instance_set = [ec2_instance, vpc_instance, nil_instance, unreal_instance] expect(aws.kill_instances(instance_set)).to be_nil end it 'cleanly handles an empty instance list' do instance_set = [] expect(aws.kill_instances(instance_set)).to be_nil end context 'in general use' do let( :instance_set ) { [ec2_instance, nil_instance, vpc_instance] } it 'terminates each running instance' do expect(mock_client).to receive(:terminate_instances).with( :instance_ids => [ec2_instance.instance_id, vpc_instance.instance_id], ) expect(kill_instances).to be_nil end it 'verifies instances exist in AWS' do instance_set.compact.each do |instance| expect(aws).to receive(:instance_by_id).with(instance.instance_id) end expect(kill_instances).to be_nil end end context 'for a single running instance' do let( :instance_set ) { [ec2_instance] } it 'terminates the running instance' do expect(mock_client).to receive(:terminate_instances).with( :instance_ids => [ec2_instance.instance_id], ) expect(kill_instances).to be_nil end it 'verifies instance exists in AWS' do instance_set.each do |instance| expected_state = instance_double(Aws::EC2::Types::InstanceState, :name => 'running') expect(instance).to receive(:state).and_return(expected_state) end expect(mock_client).to receive(:terminate_instances).with( :instance_ids => [ec2_instance.instance_id], ) expect(kill_instances).to be_nil end end context 'when an instance does not exist' do let( :instance_set ) { [unreal_instance] } it 'does not call terminate' do expect(mock_client).not_to receive(:terminate_instances) expect(kill_instances).to be_nil end it 'verifies instance does not exist' do instance_set.each do |instance| expected_state = instance_double(Aws::EC2::Types::InstanceState, :name => 'terminated') expect(instance).to receive(:state).and_return(expected_state) end expect(mock_client).not_to receive(:terminate_instances) expect(kill_instances).to be_nil end end context 'when an instance is nil' do let(:instance_set) { [nil_instance] } it 'does not call terminate' do expect(mock_client).not_to receive(:terminate_instances) expect(kill_instances).to be_nil end end end describe '#cleanup' do subject(:cleanup) { aws.cleanup } let(:ec2_instance) do instance_double(Aws::EC2::Types::Instance, :instance_id => 'id', :state => instance_double(Aws::EC2::Types::InstanceState, :name => 'running'), ) end context 'with a list of hosts' do before :each do @hosts.each { |host| host['instance'] = ec2_instance } expect(aws).to receive(:delete_key_pair_all_regions) end it 'kills instances' do expect(aws).to receive(:kill_instances).once expect(cleanup).to be_nil end end context 'with an empty host list' do before :each do @hosts = [] expect(aws).to receive( :delete_key_pair_all_regions ) end it 'kills instances' do expect(aws).to receive(:kill_instances).once expect(cleanup).to be_nil end end end describe '#log_instances', :wip do end describe '#instance_by_id', :wip do end describe '#instances', :wip do end describe '#vpc_by_id', :wip do end describe '#vpcs', :wip do end describe '#security_group_by_id', :wip do end describe '#security_groups', :wip do end describe '#kill_zombies' do it 'calls delete_key_pair_all_regions' do allow(aws).to receive(:regions).and_return([]) expect(aws).to receive(:kill_instances).once expect(aws).to receive(:delete_key_pair_all_regions).once aws.kill_zombies end end describe '#kill_zombie_volumes', :wip do end describe '#create_instance', :wip do end describe '#launch_nodes_on_some_subnet', :wip do end describe '#launch_all_nodes', :wip do end describe '#wait_for_status' do let( :aws_instance ) { instance_double(Aws::EC2::Types::Instance, :instance_id => "ec2") } let( :instance_set ) { [{:instance => aws_instance, :host => instance_double(Beaker::Host, :name => 'test')}] } subject(:wait_for_status) { aws.wait_for_status(:running, instance_set) } def mock_instance(state, other = {}) r = instance_double( Aws::EC2::Types::Instance, :instance_id => 'ec2', :state => instance_double(Aws::EC2::Types::InstanceState, :name => state), ) other.each do |k, v| allow(r).to receive(:[]).with(k).and_return(v) end r end context 'single instance' do it 'behaves correctly in typical case' do allow(aws).to receive(:instance_by_id).with('ec2').and_return(mock_instance(:waiting), mock_instance(:waiting), mock_instance(:running)) expect(aws).to receive(:backoff_sleep).exactly(3).times expect(wait_for_status).to eq(instance_set) end it 'executes block correctly instead of status if given one' do barn_value = 'did you grow up in a barn?' expect(aws).to receive(:instance_by_id).and_return(mock_instance(:running, :barn => barn_value)) expect(aws).to receive(:backoff_sleep).exactly(1).times aws.wait_for_status(:running, instance_set) do |instance| expect( instance[:barn] ).to be === barn_value true end end end context 'with multiple instances' do let(:instance_set) do [ { :instance => aws_instance, :host => instance_double(Beaker::Host, :name => 'test1') }, { :instance => aws_instance, :host => instance_double(Beaker::Host, :name => 'test2') }, ] end it 'returns the instance set passed to it' do allow(aws).to receive(:instance_by_id).and_return( mock_instance(:waiting), mock_instance(:waiting), mock_instance(:running), mock_instance(:waiting), mock_instance(:waiting), mock_instance(:running) ) allow(aws).to receive(:backoff_sleep).exactly(6).times expect(wait_for_status).to eq(instance_set) end it 'calls backoff_sleep once per instance.status call' do allow(aws).to receive(:instance_by_id).and_return( mock_instance(:waiting), mock_instance(:waiting), mock_instance(:running), mock_instance(:waiting), mock_instance(:waiting), mock_instance(:running), ) expect(aws).to receive(:backoff_sleep).exactly(6).times expect(wait_for_status).to eq(instance_set) end it 'executes block correctly instead of status if given one' do barn_value = 'did you grow up in a barn?' not_barn_value = 'notabarn' allow(aws_instance).to receive( :[] ).with( :barn ).and_return(not_barn_value, barn_value, not_barn_value, barn_value) allow(aws).to receive(:instance_by_id).and_return( mock_instance(:waiting, :barn => not_barn_value), mock_instance(:waiting, :barn => barn_value), mock_instance(:waiting, :barn => not_barn_value), mock_instance(:waiting, :barn => barn_value), ) expect(aws).to receive(:backoff_sleep).exactly(4).times aws.wait_for_status(:running, instance_set) do |instance| instance[:barn] == barn_value end end end context 'after 10 tries' do it 'raises RuntimeError' do expect(aws).to receive(:instance_by_id).and_return(mock_instance(:waiting)).exactly(10).times expect(aws).to receive(:backoff_sleep).exactly(9).times expect { wait_for_status }.to raise_error('Instance never reached state running') end it 'still raises RuntimeError if given a block' do expect(aws).to receive(:instance_by_id).and_return(mock_instance(:waiting)).exactly(10).times expect(aws).to receive(:backoff_sleep).exactly(9).times expect { wait_for_status { false } }.to raise_error('Instance never reached state running') end end end describe '#add_tags' do let(:aws_instance) { instance_double(Aws::EC2::Types::Instance, :instance_id => 'id-123') } let(:mock_client) { instance_double(Aws::EC2::Client) } subject(:add_tags) { aws.add_tags } before(:each) do allow(aws).to receive(:client).and_return(mock_client) allow(mock_client).to receive(:create_tags) end it 'returns nil' do @hosts.each {|host| host['instance'] = aws_instance} expect(add_tags).to be_nil end context 'with multiple hosts' do before :each do @hosts.each_with_index do |host, i| host['instance'] = instance_double(Aws::EC2::Types::Instance, :instance_id => "id-#{i}") end end it 'handles host_tags hash on host object' do # set :host_tags on first host @hosts[0][:host_tags] = {'test_tag' => 'test_value'} expect(mock_client).to receive(:create_tags).with( :resources => [@hosts[0]['instance'].instance_id], :tags => include( { :key => 'test_tag', :value => 'test_value', }, ), ).once expect(add_tags).to be_nil end it 'adds tag for jenkins_build_url' do aws.instance_eval('@options[:jenkins_build_url] = "my_build_url"') expect(mock_client).to receive(:create_tags).with( :resources => anything, :tags => include( { :key => 'jenkins_build_url', :value => 'my_build_url', }, ), ).at_least(:once) expect(add_tags).to be_nil end it 'adds tag for Name' do expect(mock_client).to receive(:create_tags).with( :resources => anything, :tags => include( { :key => 'Name', :value => a_string_matching(/vm/), }, ), ).at_least(:once) expect(add_tags).to be_nil end it 'adds tag for department' do aws.instance_eval('@options[:department] = "my_department"') expect(mock_client).to receive(:create_tags).with( :resources => anything, :tags => include( { :key => 'department', :value => 'my_department', }, ), ).at_least(:once) expect(add_tags).to be_nil end it 'adds tag for project' do aws.instance_eval('@options[:project] = "my_project"') expect(mock_client).to receive(:create_tags).with( :resources => anything, :tags => include( { :key => 'project', :value => 'my_project', }, ), ).at_least(:once) expect(add_tags).to be_nil end it 'adds tag for created_by' do aws.instance_eval('@options[:created_by] = "my_created_by"') expect(mock_client).to receive(:create_tags).with( :resources => anything, :tags => include( { :key => 'created_by', :value => 'my_created_by', }, ), ).at_least(:once) expect(add_tags).to be_nil end end end describe '#populate_dns' do let( :vpc_instance ) do instance_double(Aws::EC2::Types::Instance, public_ip_address: nil, private_ip_address: "vpc_private_ip", public_dns_name: "vpc_dns_name") end let( :ec2_instance ) do instance_double(Aws::EC2::Types::Instance, public_ip_address: "ec2_public_ip", private_ip_address: "ec2_private_ip", public_dns_name: "ec2_dns_name") end subject(:populate_dns) { aws.populate_dns } subject(:hosts) { aws.instance_variable_get(:@hosts) } context 'on a public EC2 instance' do before :each do @hosts.each { |host| host['instance'] = ec2_instance } populate_dns end it 'sets host ip to instance.public_ip_address' do hosts.each do |host| expect(host['ip']).to eql(ec2_instance.public_ip_address) end end it 'sets host private_ip to instance.private_ip_address' do hosts.each do |host| expect(host['private_ip']).to eql(ec2_instance.private_ip_address) end end it 'sets host dns_name to instance.public_dns_name' do hosts.each do |host| expect(host['dns_name']).to eql(ec2_instance.public_dns_name) end end end context 'on a VPC based instance' do before :each do @hosts.each { |host| host['instance'] = vpc_instance } populate_dns end it 'sets host ip to instance.private_ip_address' do hosts.each do |host| expect(host['ip']).to eql(vpc_instance.private_ip_address) end end it 'sets host private_ip to instance.private_ip_address' do hosts.each do |host| expect(host['private_ip']).to eql(vpc_instance.private_ip_address) end end it 'sets host dns_name to instance.public_dns_name' do hosts.each do |host| expect(host['dns_name']).to eql(vpc_instance.public_dns_name) end end end end describe '#etc_hosts_entry' do let( :host ) { @hosts[0] } let( :interface ) { :ip } subject(:etc_hosts_entry) { aws.etc_hosts_entry(host, interface) } it 'returns a predictable host entry' do expect(aws).to receive(:get_domain_name).and_return('lan') expect(etc_hosts_entry).to eq("ip.address.for.vm1\tvm1 vm1.lan vm1.box.tld\n") end context 'when :private_ip is requested' do let( :interface ) { :private_ip } it 'returns host entry for the private_ip' do host = @hosts[0] expect(aws).to receive(:get_domain_name).and_return('lan') expect(etc_hosts_entry).to eq("private.ip.for.vm1\tvm1 vm1.lan vm1.box.tld\n") end end end describe '#configure_hosts' do subject(:configure_hosts) { aws.configure_hosts } it { is_expected.to be_nil } context 'calls #set_etc_hosts' do it 'for each host (except the f5 and windows ones)' do non_netdev_windows_hosts = @hosts.select{ |h| !(h['platform'] =~ /f5|netscaler|windows/) } expect(aws).to receive(:set_etc_hosts).exactly(non_netdev_windows_hosts.size).times expect(configure_hosts).to be_nil end it 'with predictable host entries' do @hosts = [@hosts[0], @hosts[1]] entries = "127.0.0.1\tlocalhost localhost.localdomain\n"\ "private.ip.for.vm1\tvm1 vm1.lan vm1.box.tld\n"\ "ip.address.for.vm2\tvm2 vm2.lan vm2.box.tld\n" allow(aws).to receive(:get_domain_name).and_return('lan') expect(aws).to receive(:set_etc_hosts).with(@hosts[0], entries) expect(aws).to receive(:set_etc_hosts).with(@hosts[1], anything) expect(configure_hosts).to be_nil end end end describe '#enable_root_on_hosts' do context 'enabling root shall be called once for the ubuntu machine' do it "should enable root once" do allow(aws).to receive(:enable_root_netscaler) expect( aws ).to receive(:copy_ssh_to_root).with( @hosts[3], options ).once() expect( aws ).to receive(:enable_root_login).with( @hosts[3], options).once() aws.enable_root_on_hosts(); end end it 'enables root once on the f5 host through its code path' do allow(aws).to receive(:enable_root_netscaler) expect( aws ).to receive(:enable_root_f5).with( @hosts[4] ).once() aws.enable_root_on_hosts() end context 'when :disable_root_ssh is true' do it 'should not enable root' do @hosts = [@hosts[0]] @hosts[0][:disable_root_ssh] = true expect( aws ).to_not receive(:enable_root) aws.enable_root_on_hosts(); end end context 'when :disable_root_ssh is defined and false' do it 'should enable root' do @hosts = [@hosts[0]] @hosts[0][:disable_root_ssh] = false expect( aws ).to receive(:enable_root).with(@hosts[0]).once() aws.enable_root_on_hosts(); end end end describe '#enable_root_f5' do let( :f5_host ) { @hosts[4] } subject(:enable_root_f5) { aws.enable_root_f5(f5_host) } it 'creates a password on the host' do result_mock = Beaker::Result.new(f5_host, '') result_mock.exit_code = 0 allow( f5_host ).to receive( :exec ).and_return(result_mock) allow( aws ).to receive( :backoff_sleep ) sha_mock = Object.new allow( Digest::SHA256 ).to receive( :new ).and_return(sha_mock) expect( sha_mock ).to receive( :hexdigest ).once() enable_root_f5 end it 'tries 10x before failing correctly' do result_mock = Beaker::Result.new(f5_host, '') result_mock.exit_code = 2 allow( f5_host ).to receive( :exec ).and_return(result_mock) expect( aws ).to receive( :backoff_sleep ).exactly(9).times expect{ enable_root_f5 }.to raise_error( RuntimeError, /unable/ ) end end describe '#enable_root_netscaler' do let(:ns_host) { @hosts[5] } subject(:enable_root_netscaler) { aws.enable_root_netscaler(ns_host) } it 'set password to instance id of the host' do ns_host["instance"] = instance_double(Aws::EC2::Types::Instance, :instance_id => 'i-842018') enable_root_netscaler expect(ns_host['ssh'][:password]).to eql("i-842018") end end describe '#set_hostnames' do subject(:set_hostnames) { aws.set_hostnames } it 'returns @hosts' do expect(set_hostnames).to eq(@hosts) end context 'for each host' do it 'calls exec' do @hosts.each do |host| expect(host).to receive(:exec).once unless host['platform'] =~ /netscaler|windows/ end expect(set_hostnames).to eq(@hosts) end it 'passes a Command instance to exec' do @hosts.each do |host| expect(host).to receive(:exec).with( instance_of(Beaker::Command) ).once unless host['platform'] =~ /netscaler|windows/ end expect(set_hostnames).to eq(@hosts) end it 'sets the the vmhostname to the dns_name for each host' do expect(set_hostnames).to eq(@hosts) @hosts.each do |host| expect(host[:vmhostname]).to eq(host[:dns_name]) expect(host[:vmhostname]).to eq(host.hostname) end end it 'sets the the vmhostname to the beaker config name for each host' do options[:use_beaker_hostnames] = true @hosts.each do |host| host.instance_eval("@name = 'prettyponyprincess'") end expect(set_hostnames).to eq(@hosts) @hosts.each do |host| expect(host[:vmhostname]).not_to eq(nil) expect(host[:vmhostname]).to eq(host.name) expect(host[:vmhostname]).to eq(host.hostname) end end end context 'windows host platform' do it 'should not change hostname when use_beaker_hostnames is enabled' do options[:use_beaker_hostnames] = true expect(@hosts[6]).to_not receive(:exec) end end end describe '#backoff_sleep' do it "should call sleep 1024 times at attempt 10" do expect_any_instance_of( Object ).to receive(:sleep).with(1024) aws.backoff_sleep(10) end end describe '#public_key' do subject(:public_key) { aws.public_key } it "retrieves contents from local ~/.ssh/id_rsa.pub file" do # Stub calls to file read/exists key_value = 'foobar_Rsa' allow(File).to receive(:exist?).with(/id_dsa.pub/) { false } allow(File).to receive(:exist?).with(/id_dsa/) { false } allow(File).to receive(:exist?).with(/id_rsa.pub/) { true } allow(File).to receive(:exist?).with(/id_rsa/) { true } allow(File).to receive(:read).with(/id_rsa.pub/) { key_value } # Should return contents of allow( previously ).to receivebed id_rsa.pub expect(public_key).to be === key_value end it "retrieves contents from local ~/.ssh/id_dsa.pub file" do # Stub calls to file read/exists key_value = 'foobar_Dsa' allow(File).to receive(:exist?).with(/id_rsa.pub/) { false } allow(File).to receive(:exist?).with(/id_rsa/) { false } allow(File).to receive(:exist?).with(/id_dsa.pub/) { true } allow(File).to receive(:exist?).with(/id_dsa/) { true } allow(File).to receive(:read).with(/id_dsa.pub/) { key_value } expect(public_key).to be === key_value end it "should return an error if the files do not exist" do allow(File).to receive(:exist?).with(/id_[dr]sa.pub/) { false } allow(File).to receive(:exist?).with(/id_[dr]sa/) { false } expect { public_key }.to raise_error(RuntimeError, /Expected to find a public key/) end it "uses options-provided keys" do opts = aws.instance_variable_get( :@options ) opts[:ssh][:keys] = ['fake_key1', 'fake_key2'] aws.instance_variable_set( :@options, opts ) key_value = 'foobar_Custom2' allow(File).to receive(:exist?).with(anything) { false } allow(File).to receive(:exist?).with(/fake_key2/) { true } allow(File).to receive(:read).with(/fake_key2/) { key_value } expect(public_key).to be === key_value end end describe '#key_name' do it 'returns a key name from the local hostname' do # Mock out the hostname and local user calls expect( Socket ).to receive(:gethostname) { "foobar" } expect( aws ).to receive(:local_user) { "bob" } # Should match the expected composite key name expect(aws.key_name).to match(/^Beaker-bob-foobar-/) end it 'uses the generated random string from :aws_keyname_modifier' do expect(aws.key_name).to match(/#{options[:aws_keyname_modifier]}/) end it 'uses nanosecond time value to make key name collision harder' do options[:timestamp] = Time.now nanosecond_value = options[:timestamp].strftime("%N") expect(aws.key_name).to match(/#{nanosecond_value}/) end end describe '#local_user' do it 'returns ENV["USER"]' do stub_const('ENV', ENV.to_hash.merge('USER' => 'SuperUser')) expect(aws.local_user).to eq("SuperUser") end end describe '#ensure_key_pair' do let( :region ) { double('region', :name => 'test_region_name') } subject(:ensure_key_pair) { aws.ensure_key_pair(region) } let( :key_name ) { "Beaker-rspec-SUT" } it 'deletes the given keypair, then recreates it' do allow( aws ).to receive( :key_name ).and_return(key_name) expect( aws ).to receive( :delete_key_pair ).with( region, key_name).once.ordered expect( aws ).to receive( :create_new_key_pair ).with( region, key_name).once.ordered ensure_key_pair end end describe '#delete_key_pair_all_regions' do before(:each) do allow(aws).to receive(:my_key_pairs).and_return(region_keypairs) end after(:each) do aws.delete_key_pair_all_regions end let(:region_keypairs) do { 'test1' => ['key1', 'key2', 'key3'], 'test2' => ['key4', 'key5', 'key6'], } end it 'calls delete_key_pair over all regions' do region_keypairs.each do |region, keyname_array| keyname_array.each do |keyname| expect(aws).to receive(:delete_key_pair).with(region, keyname) end end end end describe '#my_key_pairs' do let(:regions) { ['name1', 'name2'] } let(:mock_clients) { regions.map { |r| [r, instance_double(Aws::EC2::Client)] }.to_h } let(:default_key_name) { 'test_pair_6193' } before(:each) do allow(aws).to receive(:regions).and_return(regions) allow(aws).to receive(:key_name).and_return(default_key_name) regions.each do |region| allow(aws).to receive(:client).with(region).and_return(mock_clients[region]) end end it 'uses the default keyname if no filter is given' do regions.each do |region| expect(mock_clients[region]).to receive(:describe_key_pairs).with( :filters => [{ :name => 'key-name', :values => [default_key_name] }] ).and_return(instance_double(Aws::EC2::Types::DescribeKeyPairsResult, :key_pairs => [])) end aws.my_key_pairs() end it 'uses the filter passed if given' do name_filter = 'filter_pair_1597' filter_pattern = "#{name_filter}-*" regions.each do |region| expect(mock_clients[region]).to receive(:describe_key_pairs).with( :filters => [{ :name => 'key-name', :values => [filter_pattern] }] ).and_return(instance_double(Aws::EC2::Types::DescribeKeyPairsResult, :key_pairs => [])) end aws.my_key_pairs(name_filter) end end describe '#delete_key_pair' do let(:region) { 'test_region_name' } let(:mock_client) { instance_double(Aws::EC2::Client) } let(:pair_name) { 'pair1' } before(:each) do allow(aws).to receive(:client).with(region).and_return(mock_client) allow(mock_client).to receive(:describe_key_pairs).with( :key_names => [pair_name] ).and_return(instance_double(Aws::EC2::Types::DescribeKeyPairsResult, :key_pairs => result)) end after(:each) do aws.delete_key_pair(region, pair_name) end context 'when the keypair exists' do let(:result) { [instance_double(Aws::EC2::Types::KeyPairInfo)] } it 'deletes the keypair' do expect(mock_client).to receive(:delete_key_pair).with(:key_name => pair_name) end end context 'when the keypair does not exist' do let(:result) { [] } it 'does not try to delete the keypair' do expect(mock_client).not_to receive(:delete_key_pair) end end end describe '#create_new_key_pair' do let(:region) { 'test_region_name' } let(:ssh_string) { 'ssh_string_test_0867' } let(:pair) { instance_double(Aws::EC2::Types::KeyPairInfo) } let(:pair_name) { 'pair_name_1555432' } let(:mock_client) { instance_double(Aws::EC2::Client) } before :each do allow(aws).to receive(:client).with(region).and_return(mock_client) allow(aws).to receive(:public_key).and_return(ssh_string) allow(mock_client).to receive(:import_key_pair).with( :key_name => pair_name, :public_key_material => ssh_string, ) allow(mock_client).to receive(:wait_until).with(:key_pair_exists, any_args) end it 'imports the key given from public_key' do expect(mock_client).to receive(:import_key_pair).with( :key_name => pair_name, :public_key_material => ssh_string, ) aws.create_new_key_pair(region, pair_name) end it 'raises an exception if subsequent keypair check is false' do allow(mock_client).to receive(:wait_until).with(:key_pair_exists, any_args).and_raise(Aws::Waiters::Errors::WaiterFailed) expect { aws.create_new_key_pair(region, pair_name) }.to raise_error(RuntimeError, "AWS key pair #{pair_name} can not be queried, even after import") end end describe '#group_id' do it 'should return a predicatable group_id from a port list' do expect(aws.group_id([22, 1024])).to eq("Beaker-2799478787") end it 'should return a predicatable group_id from an empty list' do expect { aws.group_id([]) }.to raise_error(ArgumentError, "Ports list cannot be nil or empty") end end describe '#ensure_group' do let(:vpc) { instance_double(Aws::EC2::Types::Vpc, :vpc_id => 1) } let(:ports) { [22, 80, 8080] } let(:default_sg_cidr_ips) { ['0.0.0.0/0'] } subject(:ensure_group) { aws.ensure_group(vpc, ports) } let(:mock_client) { instance_double(Aws::EC2::Client) } before :each do allow(aws).to receive(:client).and_return(mock_client) end let(:security_group_result) do instance_double(Aws::EC2::Types::DescribeSecurityGroupsResult, :security_groups => [group]) end context 'for an existing group' do let(:group) { instance_double(Aws::EC2::Types::SecurityGroup, :group_name => 'Beaker-1521896090') } it 'returns group from vpc lookup' do allow(mock_client).to receive(:describe_security_groups).with(any_args).and_return(security_group_result) expect(ensure_group).to eq(group) end context 'during group lookup' do it 'performs group_id lookup for ports' do expect(aws).to receive(:group_id).with(ports) allow(mock_client).to receive(:describe_security_groups).with(any_args).and_return(security_group_result) expect(ensure_group).to eq(group) end it 'filters on group_id' do allow(mock_client).to receive(:describe_security_groups).with(:filters => include({:name => 'group-name', :values => ['Beaker-1521896090']})).and_return(security_group_result) expect(ensure_group).to eq(group) end end end context 'when group does not exist' do let(:group) { nil } it 'creates group if group.nil?' do expect(aws).to receive(:create_group).with(vpc, ports, default_sg_cidr_ips).and_return(group) allow(mock_client).to receive(:describe_security_groups).with(any_args).and_return(security_group_result) expect(ensure_group).to eq(group) end end end describe '#create_group' do let(:group_vpc_id) { 'vpc-someid' } let(:rv) { instance_double(Aws::EC2::Types::Vpc, :vpc_id => group_vpc_id) } let(:ports) { [22, 80, 8080] } subject(:create_group) { aws.create_group(rv, ports) } let(:group) { instance_double(Aws::EC2::Types::SecurityGroup, :group_id => 1) } let(:mock_client) { instance_double(Aws::EC2::Client) } before(:each) do allow(aws).to receive(:client).and_return(mock_client) end it 'returns a newly created group' do allow(mock_client).to receive(:create_security_group).with(any_args).and_return(group) allow(mock_client).to receive(:authorize_security_group_ingress).with(include(:group_id => group.group_id)).at_least(:once) expect(create_group).to eq(group) end it 'requests group_id for ports given' do expect(aws).to receive(:group_id).with(ports) allow(mock_client).to receive(:create_security_group).with(any_args).and_return(group) allow(mock_client).to receive(:authorize_security_group_ingress).with(include(:group_id => group.group_id)).at_least(:once) expect(create_group).to eq(group) end it 'creates group with expected arguments' do group_name = "Beaker-1521896090" group_desc = "Custom Beaker security group for #{ports.to_a}" expect(mock_client).to receive(:create_security_group).with( :group_name => group_name, :description => group_desc, ).and_return(group) allow(mock_client).to receive(:authorize_security_group_ingress).with(include(:group_id => group.group_id)).at_least(:once) expect(create_group).to eq(group) end context 'it is called with VPC as first param' do it 'creates group with expected arguments including vpc id' do group_name = "Beaker-1521896090" group_desc = "Custom Beaker security group for #{ports.to_a}" allow(rv).to receive(:is_a?).with(String).and_return(false) allow(rv).to receive(:is_a?).with(Aws::EC2::Types::Vpc).and_return(true) expect(mock_client).to receive(:create_security_group).with( :group_name => group_name, :description => group_desc, :vpc_id => group_vpc_id, ).and_return(group) allow(mock_client).to receive(:authorize_security_group_ingress).with(include(:group_id => group.group_id)).at_least(:once) expect(create_group).to eq(group) end end it 'authorizes requested ports for group' do allow(mock_client).to receive(:create_security_group).with(any_args).and_return(group) ports.each do |port| expect(mock_client).to receive(:authorize_security_group_ingress).with(include(:to_port => port)).once end expect(create_group).to eq(group) end context 'security group CIDRs are passed' do let(:sg_cidr_ips) { ['172.28.40.0/24', '172.20.112.0/20'] } subject(:create_group_with_cidr) { aws.create_group(rv, ports, sg_cidr_ips) } it 'authorizes requested CIDR for group' do allow(mock_client).to receive(:create_security_group).with(any_args).and_return(group) sg_cidr_ips.each do |cidr_ip| expect(mock_client).to receive(:authorize_security_group_ingress).with(include(:cidr_ip => cidr_ip)).exactly(3).times end expect(create_group_with_cidr).to eq(group) end end end describe '#load_fog_credentials' do # Receive#and_call_original below allows us to test the core load_fog_credentials method let(:dot_fog) { '.fog' } subject(:load_fog_credentials) { aws.load_fog_credentials(dot_fog) } it 'returns AWS::Credentials with loaded fog credentials and session token' do fog_creds = {:aws_access_key_id => 'awskey', :aws_secret_access_key => 'awspass', :aws_session_token => 'sometoken'} aws_creds = {:access_key_id => 'awskey', :secret_access_key => 'awspass', :session_token => 'sometoken'} expect(aws).to receive(:load_fog_credentials).and_call_original expect(aws).to receive(:get_fog_credentials).and_return(fog_creds) expect(load_fog_credentials).to have_attributes(aws_creds) end context 'raises errors' do it 'if missing aws_access_key_id credential' do creds = {:aws_secret_access_key => 'awspass'} err_text = "You must specify an aws_access_key_id in your .fog file (#{dot_fog}) for ec2 instances!" expect(aws).to receive(:load_fog_credentials).and_call_original expect(aws).to receive(:get_fog_credentials).and_return(creds) expect { load_fog_credentials }.to raise_error(err_text) end it 'if missing aws_secret_access_key credential' do creds = {:aws_access_key_id => 'awskey'} err_text = "You must specify an aws_secret_access_key in your .fog file (#{dot_fog}) for ec2 instances!" expect(aws).to receive(:load_fog_credentials).and_call_original expect(aws).to receive(:get_fog_credentials).and_return(creds) expect { load_fog_credentials }.to raise_error(err_text) end end end describe 'test_split_install' do it 'does not add port 8143 if master, dashboard and database are on the same host' do @hosts = [@hosts[0]] @hosts[0][:roles] = ["master", "dashboard", "database"] allow(aws).to receive(:test_split_install) expect(@hosts[0]).not_to have_key(:additional_ports) aws end it 'does not add port 8143 if host does not have master, dashboard or database at all' do @hosts = [@hosts[0]] @hosts[0][:roles] = ["agent", "frictionless"] allow(aws).to receive(:test_split_install) expect(@hosts[0]).not_to have_key(:additional_ports) aws end it 'adds port 8143 to all the hosts for split install that has either master, dashboard or database' do @hosts = [@hosts[0], @hosts[1], @hosts[2], @hosts[3]] @hosts[0][:roles] = ["master"] @hosts[1][:roles] = ["dashboard"] @hosts[2][:roles] = ["database"] @hosts[3][:roles] = ["agent"] allow(aws).to receive(:test_split_install) expect(@hosts[0]).to have_key(:additional_ports) expect(@hosts[0][:additional_ports]).to include(8143) expect(@hosts[1]).to have_key(:additional_ports) expect(@hosts[1][:additional_ports]).to include(8143) expect(@hosts[2]).to have_key(:additional_ports) expect(@hosts[2][:additional_ports]).to include(8143) expect(@hosts[3]).not_to have_key(:additional_ports) aws end end describe 'create_instance' do let(:mock_client) { instance_double(Aws::EC2::Client) } before(:each) do allow(aws).to receive(:client).and_return(mock_client) vpc = instance_double(Aws::EC2::Types::Vpc, :vpc_id => 1337) describe_vpcs_result = instance_double(Aws::EC2::Types::DescribeVpcsResult, :vpcs => [vpc]) allow(mock_client).to receive(:describe_vpcs).and_return(describe_vpcs_result) image = instance_double(Aws::EC2::Types::Image, :root_device_type => :no_device) described_images = instance_double(Aws::EC2::Types::DescribeImagesResult, :images => [image]) allow(mock_client).to receive(:describe_images).and_return(described_images) security_group = instance_double(Aws::EC2::Types::GroupIdentifier, :group_id => 69) security_groups_result = instance_double(Aws::EC2::Types::DescribeSecurityGroupsResult, :security_groups => [security_group]) allow(mock_client).to receive(:describe_security_groups).and_return(security_groups_result) key_pair = instance_double(Aws::EC2::Types::KeyPairInfo, :key_name => 'a-key') key_pair_result = instance_double(Aws::EC2::Types::DescribeKeyPairsResult, :key_pairs => [key_pair]) allow(aws).to receive(:ensure_key_pair).and_return(key_pair_result) end it 'raises an error when associate_public_ip_address is included without subnet_id' do host = @hosts[0] host['associate_public_ip_address'] = true expect{ aws.create_instance(host, amispec, nil) }.to raise_error("A subnet_id must be provided when configuring assoc_pub_ip_addr") end it 'sets associate_public_ip_address when included' do host = @hosts[0] host['associate_public_ip_address'] = true variable_path = hash_including( :network_interfaces => [hash_including(:associate_public_ip_address => true)]) reservation = instance_double(Aws::EC2::Types::Reservation, :instances => [instance_double(Aws::EC2::Types::Instance)]) expect(mock_client).to receive(:run_instances).with(variable_path).and_return(reservation) aws.create_instance(host, amispec, "subnetfoo") end it 'omits network_interfaces and associate_public_ip_address when not included, instead using subnet_id' do host = @hosts[0] variable_path = hash_including( :subnet_id => nil) reservation = instance_double(Aws::EC2::Types::Reservation, :instances => [instance_double(Aws::EC2::Types::Instance)]) expect(mock_client).to receive(:run_instances).with(variable_path).and_return(reservation) aws.create_instance(host, amispec, nil) end end end end