diff --git a/devops-client/lib/devops-client/handler/stack.rb b/devops-client/lib/devops-client/handler/stack.rb index 6729c0d..c1f4f58 100644 --- a/devops-client/lib/devops-client/handler/stack.rb +++ b/devops-client/lib/devops-client/handler/stack.rb @@ -61,11 +61,14 @@ class Stack < Handler attrs[:tags] = JSON.parse(File.read(tags_filepath)) end - json = JSON.pretty_generate( - without_bootstrap: options[:without_bootstrap] || false, - skip_rollback: options[:skip_rollback] || false, - stack_attributes: attrs + attrs.merge!( + launch_options: { + without_bootstrap: options[:without_bootstrap] || false, + skip_rollback: options[:skip_rollback] || false, + } ) + + json = JSON.pretty_generate(attrs) if question(I18n.t("handler.stack.question.create")) {puts json} job_ids = post_body "/stack", json reports_urls(job_ids) diff --git a/devops-service/app/api2/handlers/stack.rb b/devops-service/app/api2/handlers/stack.rb index 06e9249..5e0452d 100644 --- a/devops-service/app/api2/handlers/stack.rb +++ b/devops-service/app/api2/handlers/stack.rb @@ -20,18 +20,13 @@ module Devops end def create_stack - object = parser.create - stack_attrs = object['stack_attributes'] + stack_attrs = parser.create project = Devops::Db.connector.project(stack_attrs['project']) env = project.deploy_env(stack_attrs['deploy_env']) raise InvalidRecord.new("Environment '#{env.identifier}' of project '#{project.id}' has no stack template") if env.stack_template.nil? add_stack_attributes(stack_attrs, env, parser) - jid = Worker.start_async(StackBootstrapWorker, - stack_attributes: stack_attrs, - without_bootstrap: object['without_bootstrap'], - skip_rollback: object['skip_rollback'] - ) + jid = Worker.start_async(StackBootstrapWorker, stack_attributes: stack_attrs) [jid] end diff --git a/devops-service/app/api2/parsers/stack.rb b/devops-service/app/api2/parsers/stack.rb index 24d19df..de1ee1c 100644 --- a/devops-service/app/api2/parsers/stack.rb +++ b/devops-service/app/api2/parsers/stack.rb @@ -7,21 +7,10 @@ module Devops def create @body ||= create_object_from_json_body - - # temp fix to work on qa: - unless @body['stack_attributes'] - @body = { - 'stack_attributes' => @body.dup, - 'without_bootstrap' => true, - 'skip_rollback' => true - } - end - - stack_attributes = @body.fetch('stack_attributes') - project_name = check_string(stack_attributes["project"], "Parameter 'project' must be a not empty string") - env_name = check_string(stack_attributes["deploy_env"], "Parameter 'deploy_env' must be a not empty string") - check_string(stack_attributes["name"], "Parameter 'name' must be a not empty string", true, false) - list = check_array(stack_attributes["run_list"], "Parameter 'run_list' is invalid, it should be not empty array of strings", String, true, true) + project_name = check_string(@body["project"], "Parameter 'project' must be a not empty string") + env_name = check_string(@body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") + check_string(@body["name"], "Parameter 'name' must be a not empty string", true, false) + list = check_array(@body["run_list"], "Parameter 'run_list' is invalid, it should be not empty array of strings", String, true, true) Validators::Helpers::RunList.new(list).validate! unless list.nil? @body end diff --git a/devops-service/app/api2/routes/stack.rb b/devops-service/app/api2/routes/stack.rb index 2dc235c..8579971 100644 --- a/devops-service/app/api2/routes/stack.rb +++ b/devops-service/app/api2/routes/stack.rb @@ -30,19 +30,19 @@ module Devops # - Content-Type: application/json # - body : # { - # "stack_attributes": { - # "project": "project_name", - # "deploy_env": "test", - # "provider": "ec2", - # "tags": { - # "tagName": "tagValue" - # }, - # "parameters": { - # "KeyName": "Value" - # } + # "project": "project_name", + # "deploy_env": "test", + # "provider": "ec2", + # "tags": { + # "tagName": "tagValue" + # }, + # "parameters": { + # "KeyName": "Value" + # }, + # "launch_options": { + # "without_bootstrap": false, + # "skip_rollback": false # } - # "without_bootstrap": false, - # "skip_rollback": false # } # # * *Returns* : diff --git a/devops-service/db/mongo/models/stack/stack_base.rb b/devops-service/db/mongo/models/stack/stack_base.rb index 169a375..c2707be 100644 --- a/devops-service/db/mongo/models/stack/stack_base.rb +++ b/devops-service/db/mongo/models/stack/stack_base.rb @@ -8,6 +8,7 @@ module Devops include ModelWithProvider attr_accessor :parameters, :events, :stack_status, :persisting_is_locked + attr_accessor :launch_options # {'without_bootstrap' => false, 'skip_rollback' => true} set_field_validators :id, [::Validators::FieldValidator::NotNil, ::Validators::FieldValidator::FieldType::String, @@ -57,6 +58,7 @@ module Devops self.tags = attrs['tags'] || {} self.stack_status = attrs['stack_status'] self.persisting_is_locked = attrs['persisting_is_locked'] + self.launch_options = attrs['launch_options'] || {} self end @@ -72,7 +74,8 @@ module Devops owner: owner, run_list: run_list, tags: tags, - persisting_is_locked: persisting_is_locked + persisting_is_locked: persisting_is_locked, + launch_options: launch_options }.merge(provider_hash) end @@ -96,15 +99,6 @@ module Devops self.stack_status = 'NOT_FOUND' end - def resources - provider_instance.stack_resources(self) - end - - # resource_id is logical - def resource(resource_id) - provider_instance.stack_resource(self, resource_id) - end - def template_body stack_template_model.template_body end @@ -123,6 +117,14 @@ module Devops Devops::Db.connector.unlock_persisting_stack(id) end + def without_bootstrap? + launch_options['without_bootstrap'] || false + end + + def skip_rollback? + launch_options['skip_rollback'] || false + end + class << self # attrs should include: diff --git a/devops-service/messages/en.yml b/devops-service/messages/en.yml index d1d3e58..50a4fb6 100644 --- a/devops-service/messages/en.yml +++ b/devops-service/messages/en.yml @@ -31,8 +31,7 @@ en: server_bootstrap_private_ip_unset: "Server '%{server_id}' deploy failed: private ip is unset (job %{job_id})." deploy_unknown_error: "Unknown error occured during server '%{server_id}' deploy (job %{job_id})." deploy_failed: "Server '%{server_id}' deploy failed (job %{job_id})." - creating_server_unknown_error: "Unknown error occured during server '%{server_id}' creation (job %{job_id})." - creating_server_in_cloud_failed: "Server '%{server_id}' creation in cloud failed (job %{job_id})." + unknown_error: "Unknown error occured during server '%{server_id}' bootstrap or deploy (job %{job_id})." stack_creation_waiter: result: ok: | diff --git a/devops-service/migrations/20_04_2016_add_launch_options_to_stacks.js b/devops-service/migrations/20_04_2016_add_launch_options_to_stacks.js new file mode 100644 index 0000000..132ea66 --- /dev/null +++ b/devops-service/migrations/20_04_2016_add_launch_options_to_stacks.js @@ -0,0 +1,14 @@ +db.stacks.update( + {}, + { + $set: { + launch_options: { + without_bootstrap: false, + skip_rollback: false + } + } + }, + { + multi: true + } +) diff --git a/devops-service/providers/ec2.rb b/devops-service/providers/ec2.rb index cc0ea2b..8c75d42 100644 --- a/devops-service/providers/ec2.rb +++ b/devops-service/providers/ec2.rb @@ -279,10 +279,6 @@ module Provider } end - def stack_resources(stack) - cloud_formation.describe_stack_resources({'StackName' => stack.name}).body['StackResources'] - end - def stack_events(stack) cloud_formation.describe_stack_events(stack.name).body['StackEvents'].map{|se| {"timestamp" => se["Timestamp"], "stack_name" => se["StackName"], "stack_id" => se["StackId"], "event_id" => se["EventId"], "reason" => se["ResourceStatusReason"], "status" => se["ResourceStatus"]}}.sort{|se1, se2| se1["timestamp"] <=> se2["timestamp"]} end diff --git a/devops-service/spec/factories/stack.rb b/devops-service/spec/factories/stack.rb index fd58a82..9833895 100644 --- a/devops-service/spec/factories/stack.rb +++ b/devops-service/spec/factories/stack.rb @@ -10,6 +10,7 @@ FactoryGirl.define do name 'iamstack' owner 'root' run_list [] + launch_options({}) initialize_with { new(attributes.stringify_keys) } diff --git a/devops-service/spec/models/stack/stack_ec2_spec.rb b/devops-service/spec/models/stack/stack_ec2_spec.rb index 579f7d4..ee57b44 100644 --- a/devops-service/spec/models/stack/stack_ec2_spec.rb +++ b/devops-service/spec/models/stack/stack_ec2_spec.rb @@ -34,7 +34,7 @@ RSpec.describe Devops::Model::StackEc2, type: :model do describe '#to_hash_without_id' do it 'returns hash with several fields' do - expect(stack.to_hash_without_id.keys).to include('provider', :project, :deploy_env, :stack_template, :name, :owner, :run_list, :tags) + expect(stack.to_hash_without_id.keys).to include('provider', :project, :deploy_env, :stack_template, :name, :owner, :run_list, :tags, :launch_options) end end @@ -52,6 +52,37 @@ RSpec.describe Devops::Model::StackEc2, type: :model do end end + describe 'without_bootstrap?' do + it 'returns set falsey value' do + stack = described_class.new('launch_options' => {'without_bootstrap' => false}) + expect(stack.without_bootstrap?).to be false + end + + it 'returns set truthy value' do + stack = described_class.new('launch_options' => {'without_bootstrap' => true}) + expect(stack.without_bootstrap?).to be true + end + + it 'returns false by default' do + expect(described_class.new.without_bootstrap?).to be false + end + end + + describe 'skip_rollback?' do + it 'returns set falsey value' do + stack = described_class.new('launch_options' => {'skip_rollback' => false}) + expect(stack.skip_rollback?).to be false + end + + it 'returns set truthy value' do + stack = described_class.new('launch_options' => {'skip_rollback' => true}) + expect(stack.skip_rollback?).to be true + end + + it 'returns false by default' do + expect(described_class.new.skip_rollback?).to be false + end + end describe '#create_stack_in_cloud!' do it 'calls create_stack method of provider instance' do diff --git a/devops-service/spec/workers/stack_bootstrap_worker_spec.rb b/devops-service/spec/workers/stack_bootstrap_worker_spec.rb index d0b9f8f..4e2a45b 100644 --- a/devops-service/spec/workers/stack_bootstrap_worker_spec.rb +++ b/devops-service/spec/workers/stack_bootstrap_worker_spec.rb @@ -5,11 +5,13 @@ RSpec.describe StackBootstrapWorker, type: :worker, stubbed_connector: true, ini let(:stack_attrs) { attributes_for(:stack_ec2).stringify_keys } let(:worker) { described_class.new } let(:perform_with_bootstrap) { worker.perform('stack_attributes' => stack_attrs) } - let(:perform_without_bootstrap) { worker.perform('stack_attributes' => stack_attrs, 'without_bootstrap' => true) } + let(:perform_without_bootstrap) { + set_without_bootstrap_to(true) + worker.perform('stack_attributes' => stack_attrs) + } let(:executor) { instance_double(Devops::Executor::StackExecutor, wait_till_stack_is_created: true, - create_stack: Devops::Model::StackEc2.new(stack_attrs), persist_new_servers: nil, delete_stack: nil, bootstrap_just_persisted: bootstrap_result(:ok) @@ -20,10 +22,20 @@ RSpec.describe StackBootstrapWorker, type: :worker, stubbed_connector: true, ini Devops::Executor::StackExecutor::PrioritizedGroupsBootstrapper::Result.from_reason(reason) end + def set_without_bootstrap_to(value) + stack_attrs.merge!('launch_options' => {'without_bootstrap' => value}) + end + + def set_skip_rollback_to(value) + stack_attrs.merge!('launch_options' => {'skip_rollback' => value}) + end + before do allow(worker).to receive(:update_report) allow(worker).to receive(:executor) { executor } allow(stubbed_connector).to receive(:unlock_persisting_stack) + # #create_stack should be lazy evaluated because stack_attrs may change + allow(executor).to receive(:create_stack) { Devops::Model::StackEc2.new(stack_attrs) } end @@ -64,16 +76,29 @@ RSpec.describe StackBootstrapWorker, type: :worker, stubbed_connector: true, ini end end - context 'if without_bootstrap is false or not set' do + context 'if without_bootstrap is false' do it 'returns 0 when bootstraping servers was successful' do expect(perform_with_bootstrap).to eq 0 end - it 'rollbacks stack and returns 1 when a known error occured during servers bootstrap' do - allow(executor).to receive(:bootstrap_just_persisted) { bootstrap_result(:bootstrap_error) } - expect(executor).to receive(:delete_stack) - perform_with_bootstrap - expect(perform_with_bootstrap).to eq 1 + context "when a known error occured during servers bootstrap" do + before do + allow(executor).to receive(:bootstrap_just_persisted) { bootstrap_result(:bootstrap_error) } + end + + it 'rollbacks stack and returns 1 if skip_rollback is false' do + set_skip_rollback_to(false) + expect(executor).to receive(:delete_stack) + perform_with_bootstrap + expect(perform_with_bootstrap).to eq 1 + end + + it "doesn't rollback stack, but returns 1 if skip_rollback is true" do + set_skip_rollback_to(true) + expect(executor).not_to receive(:delete_stack) + perform_with_bootstrap + expect(perform_with_bootstrap).to eq 1 + end end it "doesn't rollback stack and returns 2 when a known error occured during servers deploy" do @@ -82,11 +107,24 @@ RSpec.describe StackBootstrapWorker, type: :worker, stubbed_connector: true, ini expect(perform_with_bootstrap).to eq 2 end - it 'rollbacks stack and reraises that error when an unknown error occured during servers bootsrap and deploy' do - error = StandardError.new - allow(executor).to receive(:bootstrap_just_persisted) { raise error } - expect(worker).to receive(:rollback_stack!) - expect{perform_with_bootstrap}.to raise_error(error) + context "when an unknown error occured during servers bootsrap and deploy" do + let(:error) { StandardError.new } + before do + allow(executor).to receive(:bootstrap_just_persisted) { raise error } + end + + it 'rollbacks stack and reraises that error if skip_rollback is false' do + set_skip_rollback_to(false) + expect(worker).to receive(:rollback_stack!) + expect{perform_with_bootstrap}.to raise_error(error) + end + + it "doesn't rollback stack, but reraises that error if skip_rollback is true" do + set_skip_rollback_to(true) + expect(worker).not_to receive(:rollback_stack!) + expect{perform_with_bootstrap}.to raise_error(error) + end end + end end \ No newline at end of file diff --git a/devops-service/workers/stack_bootstrap_worker.rb b/devops-service/workers/stack_bootstrap_worker.rb index d726e7e..48aa105 100644 --- a/devops-service/workers/stack_bootstrap_worker.rb +++ b/devops-service/workers/stack_bootstrap_worker.rb @@ -4,18 +4,16 @@ class StackBootstrapWorker < Worker # @options: # 'stack_attributes', required - # 'without_bootstrap', optional. false by default - # 'skip_rollback', optional. false by default def perform(options) call do puts_and_flush JSON.pretty_generate(options) stack_attrs = options.fetch('stack_attributes') - without_bootstrap = options['without_bootstrap'] || false - skip_rollback = options['skip_rollback'] || false - save_report(stack_attrs) @stack = executor.create_stack(stack_attrs) + without_bootstrap = @stack.without_bootstrap? + skip_rollback = @stack.skip_rollback? + if !executor.wait_till_stack_is_created puts_and_flush "Stack creating error" return 1