diff --git a/devops-service/spec/workers/chef_node_name_builder_spec.rb b/devops-service/spec/workers/chef_node_name_builder_spec.rb index ef14d0c..b78cec3 100644 --- a/devops-service/spec/workers/chef_node_name_builder_spec.rb +++ b/devops-service/spec/workers/chef_node_name_builder_spec.rb @@ -1,6 +1,6 @@ require 'workers/stack_bootstrap/chef_node_name_builder' RSpec.describe ChefNodeNameBuilder do - # real response + # test with real response to ensure it is processed correctly let(:server_info) do { "name"=>"stack-achuchkalov-aws-test-1455976199-master01", @@ -21,33 +21,26 @@ RSpec.describe ChefNodeNameBuilder do } } end - let(:project) { build(:project, id: 'proj', with_deploy_env_identifiers: %w(dev)) } - let(:env) { project.deploy_env('dev') } - let(:build_node_name) { - described_class.new(server_info, project, env).build_node_name + let(:node_name_builder) { + ChefNodeNameBuilder.new( + provider_server_info: server_info, + project_id: 'proj', + env_id: 'dev' + ) } + let(:build_node_name) { node_name_builder.build_node_name!({}) } def set_mask(mask) server_info['tags']['cid:node-name-mask'] = mask end describe '#build_node_name' do - it 'uses default mask ("$project-$cfname-$env")' do + it 'uses default mask (":project-:instancename-:env")' do expect(build_node_name).to eq 'proj-master01-dev' end - it 'substitutes $project, $env, $instanceid and $cfname' do - set_mask('$project/$env/$instanceid/$cfname') - expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01' - end - - it 'substitutes $time' do - set_mask('$project-$time') - expect(build_node_name).to match /proj-\d+/ - end - - it 'substitutes :project, :env, :instanceid and :cfname' do - set_mask(':project/:env/:instanceid/:cfname') + it 'substitutes :project, :env, :instanceid and :instancename' do + set_mask(':project/:env/:instanceid/:instancename') expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01' end @@ -56,14 +49,24 @@ RSpec.describe ChefNodeNameBuilder do expect(build_node_name).to match /proj-\d+/ end - it 'works with both colon and dollar variables' do - set_mask('$project/$env/:instanceid/:cfname') - expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01' + describe 'substitutes incrementers variables :increment-groupname: with numbers depending on @incrementers_values param' do + it 'starts with 01 for empty hash' do + set_mask('node-:increment-slave:') + expect(node_name_builder.build_node_name!({})).to eq 'node-01' + end + + it "continues with next values if hash isn't empty" do + set_mask('node-:increment-slave:') + expect(node_name_builder.build_node_name!({'slave' => nil})).to eq 'node-01' + expect(node_name_builder.build_node_name!({'slave' => 1})).to eq 'node-02' + expect(node_name_builder.build_node_name!({'slave' => 50})).to eq 'node-51' + end + + it 'could substitute different incrementers at once' do + set_mask('node-:increment-slave:-:increment-master:') + expect(node_name_builder.build_node_name!({'slave' => 1, 'master' => 3})).to eq 'node-02-04' + end end - it 'substitutes underscores to dashes' do - server_info['tags']['Name'] = 'server_1' - expect(build_node_name).to match 'proj-server-1-dev' - end end end \ No newline at end of file diff --git a/devops-service/spec/workers/stack_servers_persister_spec.rb b/devops-service/spec/workers/stack_servers_persister_spec.rb index 00a844e..93a7cb0 100644 --- a/devops-service/spec/workers/stack_servers_persister_spec.rb +++ b/devops-service/spec/workers/stack_servers_persister_spec.rb @@ -98,7 +98,7 @@ RSpec.describe StackServersPersister, stubbed_connector: true do persister.persist end - it 'build chef_node_name with default mask "$project-$cfname-$env"' do + it 'build chef_node_name with default mask ":project-:instancename-:env"' do expect(stubbed_connector).to receive(:server_insert) do |server| expect(server.chef_node_name).to eq 'name-server1-foo' end @@ -106,12 +106,28 @@ RSpec.describe StackServersPersister, stubbed_connector: true do end it "builds chef_node_name with custom mask if info['tags']['cid:node-name-mask'] exists" do - server_info_hash['tags']['cid:node-name-mask'] = '$project-$cfname-123' + server_info_hash['tags']['cid:node-name-mask'] = ':project-:instancename-123' expect(stubbed_connector).to receive(:server_insert) do |server| expect(server.chef_node_name).to eq 'name-server1-123' end persister.persist end + + describe 'incremented variables' do + it 'substitutes :increment-groupid: with incrementing numbers' do + allow(provider).to receive(:stack_servers) {[ + {'id' => 'server1', 'tags' => {'cid:node-name-mask' => 'node-:increment-group1:-dev'}, 'key_name' => 'key'}, + {'id' => 'server1', 'tags' => {'cid:node-name-mask' => 'node-:increment-group1:-dev'}, 'key_name' => 'key'} + ]} + expect(stubbed_connector).to receive(:server_insert) do |server| + expect(server.chef_node_name).to eq 'node-01-dev' + end.ordered + expect(stubbed_connector).to receive(:server_insert) do |server| + expect(server.chef_node_name).to eq 'node-02-dev' + end + persister.persist + end + end end end \ No newline at end of file diff --git a/devops-service/workers/stack_bootstrap/chef_node_name_builder.rb b/devops-service/workers/stack_bootstrap/chef_node_name_builder.rb index 6315696..0be1073 100644 --- a/devops-service/workers/stack_bootstrap/chef_node_name_builder.rb +++ b/devops-service/workers/stack_bootstrap/chef_node_name_builder.rb @@ -1,34 +1,63 @@ -class ChefNodeNameBuilder - DEFAULT_MASK = '$project-$cfname-$env' +# Builds node name from mask. Mask could be passed in server's +cid:node-name-mask+ tag, +# default mask is used otherwise. Mask is a string with possible several variables inserted, e.g. +# +':project/:env/:instanceid/:instancename/:increment-group1:'+ +# Variables meanings: +# - +:project+ is replaced with project name (given in constructor) +# - +:env+ is replaced with env name (given in constructor) +# - +:instanceid+ is replaced with provider instance id (fetched from server info) +# - +:instancename+ is replaced with value of Name tag (fetched from server info) +# - +:increment-groupname:+ is replaced with incremented number tied to group name. There could be several groups in one stack. +# P.S. Colons are used instead of dollar signs, because it's convinient to set nodename mask tag to AWS stack and not in template +# (you set tag once and it propagates to all instances), but stacks don't support dollar signs in tags (unlike EC2 instances). - def initialize(server_info, project, env) - @server_info, @project, @env = server_info, project, env - @mask = server_info['tags']['cid:node-name-mask'] || DEFAULT_MASK +class ChefNodeNameBuilder + DEFAULT_MASK = ':project-:instancename-:env' + + # @param attrs [Hash] should contain + # +:provider_server_info+ + # +:project_id+ + # +:env_id+ + def initialize(attrs) + @server_info = attrs[:provider_server_info] + @project, @env = attrs[:project_id], attrs[:env_id] + @mask = @server_info['tags']['cid:node-name-mask'] || DEFAULT_MASK end - def build_node_name + + # @param incrementers_values [Hash] is a hash in which key is name of a variable and value is last substituted number for that var. + # This method modifies +incrementers_values+, updating values for substituted variables. + # + # Examples (assume mask is set to +':project-master-:increment-group1:'+): + # incremeters_values = {} + # builder.build_node_name!(incremeters_values) # returns 'mpda-master-01' + # puts incremeters_values # {'group1' => 1} + # builder.build_node_name!(incremeters_values) # returns 'mpda-master-02' + # puts incremeters_values # {'group1' => 2} + def build_node_name!(incrementers_values) result = @mask.dup - replace_dollar_variables!(result) - replace_colon_variables!(result) - result.gsub!('_', '-') + replace_variables!(result) + replace_incrementers!(result, incrementers_values) result end private - def replace_dollar_variables!(result) - result.gsub!('$project', @project.id) - result.gsub!('$env', @env.identifier) - result.gsub!('$instanceid', @server_info['id']) - result.gsub!('$cfname', @server_info['tags']['Name'] || '') - result.gsub!('$time', Time.now.to_i.to_s) - end - - def replace_colon_variables!(result) - result.gsub!(':project', @project.id) - result.gsub!(':env', @env.identifier) + def replace_variables!(result) + result.gsub!(':project', @project) + result.gsub!(':env', @env) result.gsub!(':instanceid', @server_info['id']) - result.gsub!(':cfname', @server_info['tags']['Name'] || '') + result.gsub!(':instancename', @server_info['tags']['Name'] || '') result.gsub!(':time', Time.now.to_i.to_s) end + + def replace_incrementers!(result, incrementers_values) + groupname_regexp = /(?<=:increment-)\w+(?=:)/ + result.gsub!(/:increment-\w+:/) do |incrementer| + group_name = groupname_regexp.match(incrementer)[0] + prev_value = incrementers_values[group_name] || 0 + current_value = prev_value + 1 + incrementers_values[group_name] = current_value + current_value.to_s.rjust(2, '0') + end + end end \ No newline at end of file diff --git a/devops-service/workers/stack_bootstrap/stack_servers_persister.rb b/devops-service/workers/stack_bootstrap/stack_servers_persister.rb index 3a30eab..cb022f3 100644 --- a/devops-service/workers/stack_bootstrap/stack_servers_persister.rb +++ b/devops-service/workers/stack_bootstrap/stack_servers_persister.rb @@ -42,18 +42,18 @@ class StackServersPersister end # takes a hash, returns Server model - def persist_stack_server(info_hash) + def persist_stack_server(server_info) server_attrs = { - '_id' => info_hash['id'], - 'chef_node_name' => ChefNodeNameBuilder.new(info_hash, @project, @deploy_env).build_node_name, + '_id' => server_info['id'], + 'chef_node_name' => get_name_builder(server_info).build_node_name!(incrementers_values), 'created_by' => stack.owner, 'deploy_env' => @deploy_env.identifier, - 'key' => info_hash['key_name'] || @provider.ssh_key, + 'key' => server_info['key_name'] || @provider.ssh_key, 'project' => @project.id, 'provider' => @provider.name, 'remote_user' => mongo.image(@deploy_env.image).remote_user, - 'private_ip' => info_hash['private_ip'], - 'public_ip' => info_hash['public_ip'], + 'private_ip' => server_info['private_ip'], + 'public_ip' => server_info['public_ip'], 'run_list' => stack.run_list || [], 'stack' => stack.name } @@ -63,6 +63,19 @@ class StackServersPersister server end + def get_name_builder(server_info) + ChefNodeNameBuilder.new( + provider_server_info: server_info, + project_id: @project.id, + env_id: @deploy_env.identifier, + owner: stack.owner + ) + end + + def incrementers_values + @incrementers_values ||= {} + end + def mongo Devops::Db.connector end diff --git a/devops-service/workers/stack_bootstrap/stack_synchronizer.rb b/devops-service/workers/stack_bootstrap/stack_synchronizer.rb index c889f47..d420954 100644 --- a/devops-service/workers/stack_bootstrap/stack_synchronizer.rb +++ b/devops-service/workers/stack_bootstrap/stack_synchronizer.rb @@ -18,7 +18,7 @@ class StackSynchronizer stack.sync! print_new_events case stack.stack_status - when 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS' + when 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'DELETE_IN_PROGRESS' when 'CREATE_COMPLETE' ::Devops::Db.connector.stack_update(stack) puts_and_flush "Stack '#{stack.id}' status is now #{stack.stack_status}" @@ -26,6 +26,9 @@ class StackSynchronizer when 'ROLLBACK_COMPLETE' puts_and_flush "Stack '#{stack.id}' status is rolled back" return error_code(:stack_rolled_back) + when 'DELETE_COMPLETE' + puts_and_flush "Stack '#{stack.id}' status is deleted" + return error_code(:stack_deleted) else puts_and_flush "Unknown stack status: '#{stack.stack_status}'" return error_code(:unkown_status) @@ -54,7 +57,8 @@ class StackSynchronizer stack_rolled_back: 1, unkown_status: 2, timeout: 3, - error: 5 + error: 5, + stack_deleted: 6 } end