require 'lib/executors/server_executor' RSpec.describe Devops::Executor::ServerExecutor, type: :executor, stubbed_connector: true, stubbed_logger: 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' 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 creating_server_in_cloud_failed error code' do allow(provider).to receive(:create_server) { false } expect(subject).to eq 10 end end end describe '#bootstrap', 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 server_bootstrap_private_ip_unset 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 server_bootstrap_fail 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 :server_bootstrap_fail 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 describe '#two_phase_bootstrap', stubbed_knife: true do subject { executor.two_phase_bootstrap({}) } before do allow(provider).to receive(:run_list) {[]} allow(stubbed_connector).to receive(:server_delete) end context 'when bootstrap was successful' do before do allow(executor).to receive(:bootstrap) { 0 } allow(executor).to receive(:check_server_on_chef_server) { false } end context 'if node presents on chef server' do before do allow(executor).to receive(:check_server_on_chef_server) { true } allow(executor).to receive(:deploy_server) allow(stubbed_knife).to receive(:set_run_list) end it 'builds run list' do expect(executor).to receive(:compute_run_list) subject end it 'sets run list to chef node' do expect(stubbed_knife).to receive(:set_run_list) subject end it 'deploys server' do expect(executor).to receive(:deploy_server) subject end context 'if deploy was successful' do it 'returns 0' do allow(executor).to receive(:deploy_server) { 0 } expect(subject).to eq 0 end end context "if deploy wasn't successful" do it 'returns :deploy_failed code' do allow(executor).to receive(:deploy_server) { 1 } expect(subject).to eq 8 end end context 'when an error occured during deploy' do it 'returns :deploy_unknown_error code' do allow(executor).to receive(:deploy_server) { raise } expect(subject).to eq 6 end end end context "if node doesn't present on chef server" do it 'roll backs and then deletes server from mongo' do allow(executor).to receive(:check_server_on_chef_server) { false } allow(executor).to receive(:roll_back) allow(stubbed_connector).to receive(:server_delete) expect(executor).to receive(:roll_back).ordered expect(stubbed_connector).to receive(:server_delete).ordered subject end end end context "when bootstrap wasn't successful" do it 'returns :server_bootstrap_fail error code' do allow(executor).to receive(:bootstrap) { 1 } expect(subject).to eq 2 end end context 'when an error occured during bootstrap' do it 'returns :server_bootstrap_unknown_error error code' do allow(executor).to receive(:bootstrap) { raise } expect(subject).to eq 7 end end end describe '#check_server_on_chef_server', stubbed_knife: true do subject { executor.check_server_on_chef_server } before do server.chef_node_name = 'a' allow(stubbed_knife).to receive(:chef_node_list) { @node_list } allow(stubbed_knife).to receive(:chef_client_list) { @client_list } end it 'returns true when node_name in node list and in client list' do @node_list = %w(a); @client_list = %w(a) expect(subject).to be true end it "returns false if node name isn't in node list" do @node_list = []; @client_list = %w(a) expect(subject).to be false end it "returns false if node name isn't in node list" do @node_list = %w(a); @client_list = [] expect(subject).to be false end end end