require 'lib/executors/server_executor' RSpec.describe Devops::Executor::ServerExecutor, type: :executor, stubbed_connector: true do let(:project) { build(:project) } let(:deploy_env) { project.deploy_env('foo') } let(:server) { build(:server, project: project.id, deploy_env: 'foo') } let(:output) { File.open(File::NULL, "w") } let(:provider) { double('Provider instance') } let(:executor) { described_class.new(server, output) } before do allow(stubbed_connector).to receive(:project) { project } allow(executor.deploy_env).to receive(:provider_instance) { provider } allow(server).to receive(:provider_instance) { provider } end describe '#initialize' do it 'sets server, project, deploy_env, out instance variables' do expect(executor.server).to eq server expect(executor.deploy_env).to eq deploy_env expect(executor).to have_instance_variable_value(:project, project) expect(executor).to have_instance_variable_value(:out, output) end it 'defines :flush method on @out if it is absent' do out = Class.new.new expect(out).not_to respond_to(:flush) described_class.new(server, out) expect(out).to respond_to(:flush) end it 'sets current_user from options' do user = double executor = described_class.new(server, '', {current_user: user}) expect(executor).to have_instance_variable_value(:current_user, user) end end describe '#create_server_object' do it 'builds Server object' do server = executor.create_server_object('created_by' => 'me') expect(server).to be_a(Devops::Model::Server) expect(server.project).to eq 'my_project' expect(server.deploy_env).to eq 'foo' expect(server.created_by).to eq 'me' end end describe '#create_server', stubbed_connector: true, stubbed_logger: true do let!(:without_bootstrap) { @without_bootstrap = true } let!(:run_list) { @run_list = %w(role[asd]) } let!(:key) { @key = 'key' } let!(:image) { double('Image instance', remote_user: 'remote_user') } subject { executor.create_server( 'created_by' => 'user', 'run_list' => @run_list, 'name' => 'node_name', 'key' => @key, 'without_bootstrap' => @without_bootstrap ) } before do allow(provider).to receive(:create_server) { true } allow(stubbed_connector).to receive(:image) { image } allow(stubbed_connector).to receive(:server_insert) end it 'builds server model from given options' do subject expect(executor.server.created_by).to eq 'user' expect(executor.server.chef_node_name).to eq 'node_name' expect(executor.server.key).to eq @key expect(executor.server.run_list).to eq @run_list end it 'sets run list to an empty array by default' do @run_list = nil subject expect(executor.server.run_list).to eq [] end it 'sets key to default provider ssh key by default' do @key = nil allow(provider).to receive(:ssh_key) { 'default_key' } subject expect(executor.server.key).to eq 'default_key' end it 'runs hooks' do expect(executor).to receive(:run_hook).with(:before_create).ordered expect(executor).to receive(:run_hook).with(:after_create).ordered subject end it 'creates server in cloud' do expect(provider).to receive(:create_server).with( an_instance_of(Devops::Model::Server), deploy_env.image, deploy_env.flavor, deploy_env.subnets, deploy_env.groups, output ) subject end it 'inserts built server into mongo' do expect(stubbed_connector).to receive(:server_insert) subject end it 'schedules expiration for server' do expect(executor).to receive(:schedule_expiration).with(an_instance_of(Devops::Model::Server)) subject end context 'without_bootstrap option is false' do it 'launches bootstrap' do @without_bootstrap = false allow(image).to receive(:bootstrap_template) { 'template' } allow(executor).to receive(:two_phase_bootstrap) expect(executor).to receive(:two_phase_bootstrap) subject end end context 'without_bootstrap option is nil' do it 'launches bootstrap' do @without_bootstrap = nil allow(image).to receive(:bootstrap_template) { 'template' } allow(executor).to receive(:two_phase_bootstrap) expect(executor).to receive(:two_phase_bootstrap) subject end end context 'without_bootstrap option is true' do it "doesn't launch bootstrap" do @without_bootstrap = true expect(executor).not_to receive(:two_phase_bootstrap) subject end end context 'if error has been raised during execution' do before do allow(stubbed_connector).to receive(:server_delete) allow(provider).to receive(:create_server) { raise } end it 'rollbacks server creating' do expect(executor).to receive(:roll_back) subject end it 'deletes server from mongo' do expect(stubbed_connector).to receive(:server_delete) subject end end context "if creating server in cloud wasn't successful" do it 'returns proper error code' do allow(provider).to receive(:create_server) { false } expect(subject).to eq 10 end end end describe '#bootstrap', stubbed_logger: true, stubbed_knife: true do subject { executor.bootstrap({}) } let(:image) { double('Key instance', path: 'path') } before do allow(executor).to receive(:sleep) allow(executor).to receive(:connected_successfully?).and_return(true) allow(executor).to receive(:execute_system_command) allow(provider).to receive(:create_default_chef_node_name).and_return('chef_node') allow(stubbed_connector).to receive(:key).and_return(image) allow(stubbed_connector).to receive(:server_set_chef_node_name) allow(stubbed_knife).to receive(:knife_bootstrap).and_return(0) end it 'run before hook' do expect(executor).to receive(:run_hook).with(:before_bootstrap, output).ordered expect(executor).to receive(:run_hook).with(:after_bootstrap, output).ordered subject end context "when server's private ip is unset" do it 'returns proper error code' do server.private_ip = nil expect(subject).to eq 3 end end it 'tries to ssh to server' do expect(executor).to receive(:execute_system_command).with(/ssh/) subject end context "couldn't ssh to server" do before { allow(executor).to receive(:connected_successfully?) { false } } it 'tries to ssh to server maximum MAX_SSH_RETRIES_AMOUNT times' do max_retries = Devops::Executor::ServerExecutor::MAX_SSH_RETRIES_AMOUNT expect(executor).to receive(:execute_system_command).exactly(max_retries).times subject end it 'returns proper error code' do expect(subject).to eq 2 end end context 'after successful ssh check' do before { allow(executor).to receive(:connected_successfully?).and_return(false, true) } it "sets default chef node name if it's nil" do executor.server.chef_node_name = nil expect {subject}.to change {executor.server.chef_node_name}.to 'chef_node' end it 'executes knife bootstrap' do expect(stubbed_knife).to receive(:knife_bootstrap).with(output, server.private_ip, instance_of(Array)) subject end it "bootstraps to public ip if it's set" do server.public_ip = '8.8.8.8' expect(stubbed_knife).to receive(:knife_bootstrap).with(output, '8.8.8.8', instance_of(Array)) subject end context 'after successful bootstrap' do it "updates server's chef node name in db" do expect(stubbed_connector).to receive(:server_set_chef_node_name).with(instance_of(Devops::Model::Server)) subject end end context "if bootstraping wasn't successful" do before { allow(stubbed_knife).to receive(:knife_bootstrap).and_return(123) } it 'returns proper code' do expect(subject).to eq 2 end it "doesn't run after hook" do expect(executor).to receive(:run_hook).with(:before_bootstrap, output) subject end end end end end