Merge branch 'CID-428-increments_in_stack_node_names' into features

This commit is contained in:
Anton Chuchkalov 2016-03-04 14:19:06 +03:00
commit 13b3d56ab9
6 changed files with 122 additions and 57 deletions

View File

@ -54,8 +54,8 @@ module Devops
self.parameters = attrs['parameters'] self.parameters = attrs['parameters']
self.owner = attrs['owner'] self.owner = attrs['owner']
self.run_list = attrs['run_list'] || [] self.run_list = attrs['run_list'] || []
self.stack_status = attrs['stack_status']
self.tags = attrs['tags'] || {} self.tags = attrs['tags'] || {}
self.stack_status = attrs['stack_status']
self self
end end

View File

@ -1,6 +1,6 @@
require 'workers/stack_bootstrap/chef_node_name_builder' require 'workers/stack_bootstrap/chef_node_name_builder'
RSpec.describe ChefNodeNameBuilder do RSpec.describe ChefNodeNameBuilder do
# real response # test with real response to ensure it is processed correctly
let(:server_info) do let(:server_info) do
{ {
"name"=>"stack-achuchkalov-aws-test-1455976199-master01", "name"=>"stack-achuchkalov-aws-test-1455976199-master01",
@ -21,33 +21,26 @@ RSpec.describe ChefNodeNameBuilder do
} }
} }
end end
let(:project) { build(:project, id: 'proj', with_deploy_env_identifiers: %w(dev)) } let(:node_name_builder) {
let(:env) { project.deploy_env('dev') } ChefNodeNameBuilder.new(
let(:build_node_name) { provider_server_info: server_info,
described_class.new(server_info, project, env).build_node_name project_id: 'proj',
env_id: 'dev'
)
} }
let(:build_node_name) { node_name_builder.build_node_name!({}) }
def set_mask(mask) def set_mask(mask)
server_info['tags']['cid:node-name-mask'] = mask server_info['tags']['cid:node-name-mask'] = mask
end end
describe '#build_node_name' do 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' expect(build_node_name).to eq 'proj-master01-dev'
end end
it 'substitutes $project, $env, $instanceid and $cfname' do it 'substitutes :project, :env, :instanceid and :instancename' do
set_mask('$project/$env/$instanceid/$cfname') set_mask(':project/:env/:instanceid/:instancename')
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')
expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01' expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01'
end end
@ -56,14 +49,24 @@ RSpec.describe ChefNodeNameBuilder do
expect(build_node_name).to match /proj-\d+/ expect(build_node_name).to match /proj-\d+/
end end
it 'works with both colon and dollar variables' do describe 'substitutes incrementers variables :increment-groupname: with numbers depending on @incrementers_values param' do
set_mask('$project/$env/:instanceid/:cfname') it 'starts with 01 for empty hash' do
expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01' set_mask('node-:increment-slave:')
expect(node_name_builder.build_node_name!({})).to eq 'node-01'
end end
it 'substitutes underscores to dashes' do it "continues with next values if hash isn't empty" do
server_info['tags']['Name'] = 'server_1' set_mask('node-:increment-slave:')
expect(build_node_name).to match 'proj-server-1-dev' 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 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
end end
end end

View File

@ -98,7 +98,7 @@ RSpec.describe StackServersPersister, stubbed_connector: true do
persister.persist persister.persist
end 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(stubbed_connector).to receive(:server_insert) do |server|
expect(server.chef_node_name).to eq 'name-server1-foo' expect(server.chef_node_name).to eq 'name-server1-foo'
end end
@ -106,12 +106,28 @@ RSpec.describe StackServersPersister, stubbed_connector: true do
end end
it "builds chef_node_name with custom mask if info['tags']['cid:node-name-mask'] exists" do 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(stubbed_connector).to receive(:server_insert) do |server|
expect(server.chef_node_name).to eq 'name-server1-123' expect(server.chef_node_name).to eq 'name-server1-123'
end end
persister.persist persister.persist
end 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
end end

View File

@ -1,34 +1,63 @@
class ChefNodeNameBuilder # Builds node name from mask. Mask could be passed in server's +cid:node-name-mask+ tag,
DEFAULT_MASK = '$project-$cfname-$env' # 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) class ChefNodeNameBuilder
@server_info, @project, @env = server_info, project, env DEFAULT_MASK = ':project-:instancename-:env'
@mask = server_info['tags']['cid:node-name-mask'] || DEFAULT_MASK
# @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 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 result = @mask.dup
replace_dollar_variables!(result) replace_variables!(result)
replace_colon_variables!(result) replace_incrementers!(result, incrementers_values)
result.gsub!('_', '-')
result result
end end
private private
def replace_dollar_variables!(result) def replace_variables!(result)
result.gsub!('$project', @project.id) result.gsub!(':project', @project)
result.gsub!('$env', @env.identifier) result.gsub!(':env', @env)
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)
result.gsub!(':instanceid', @server_info['id']) 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) result.gsub!(':time', Time.now.to_i.to_s)
end 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 end

View File

@ -42,18 +42,18 @@ class StackServersPersister
end end
# takes a hash, returns Server model # takes a hash, returns Server model
def persist_stack_server(info_hash) def persist_stack_server(server_info)
server_attrs = { server_attrs = {
'_id' => info_hash['id'], '_id' => server_info['id'],
'chef_node_name' => ChefNodeNameBuilder.new(info_hash, @project, @deploy_env).build_node_name, 'chef_node_name' => get_name_builder(server_info).build_node_name!(incrementers_values),
'created_by' => stack.owner, 'created_by' => stack.owner,
'deploy_env' => @deploy_env.identifier, 'deploy_env' => @deploy_env.identifier,
'key' => info_hash['key_name'] || @provider.ssh_key, 'key' => server_info['key_name'] || @provider.ssh_key,
'project' => @project.id, 'project' => @project.id,
'provider' => @provider.name, 'provider' => @provider.name,
'remote_user' => mongo.image(@deploy_env.image).remote_user, 'remote_user' => mongo.image(@deploy_env.image).remote_user,
'private_ip' => info_hash['private_ip'], 'private_ip' => server_info['private_ip'],
'public_ip' => info_hash['public_ip'], 'public_ip' => server_info['public_ip'],
'run_list' => stack.run_list || [], 'run_list' => stack.run_list || [],
'stack' => stack.name 'stack' => stack.name
} }
@ -63,6 +63,19 @@ class StackServersPersister
server server
end 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 def mongo
Devops::Db.connector Devops::Db.connector
end end

View File

@ -18,7 +18,7 @@ class StackSynchronizer
stack.sync! stack.sync!
print_new_events print_new_events
case stack.stack_status case stack.stack_status
when 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS' when 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'DELETE_IN_PROGRESS'
when 'CREATE_COMPLETE' when 'CREATE_COMPLETE'
::Devops::Db.connector.stack_update(stack) ::Devops::Db.connector.stack_update(stack)
puts_and_flush "Stack '#{stack.id}' status is now #{stack.stack_status}" puts_and_flush "Stack '#{stack.id}' status is now #{stack.stack_status}"
@ -26,6 +26,9 @@ class StackSynchronizer
when 'ROLLBACK_COMPLETE' when 'ROLLBACK_COMPLETE'
puts_and_flush "Stack '#{stack.id}' status is rolled back" puts_and_flush "Stack '#{stack.id}' status is rolled back"
return error_code(:stack_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 else
puts_and_flush "Unknown stack status: '#{stack.stack_status}'" puts_and_flush "Unknown stack status: '#{stack.stack_status}'"
return error_code(:unkown_status) return error_code(:unkown_status)
@ -54,7 +57,8 @@ class StackSynchronizer
stack_rolled_back: 1, stack_rolled_back: 1,
unkown_status: 2, unkown_status: 2,
timeout: 3, timeout: 3,
error: 5 error: 5,
stack_deleted: 6
} }
end end