# Copyright 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. require File.join(File.dirname(__FILE__), '../spec_helper') describe Astute::TaskDeployment do include SpecHelpers let(:ctx) do ctx = mock('context') ctx.stubs(:task_id) ctx end let(:tasks_metadata) do { 'fault_tolerance_groups' =>[ {"fault_tolerance"=>0, "name"=>"primary-controller", "node_ids"=>["1"]}, {"fault_tolerance"=>1, "name"=>"controller", "node_ids"=>[]}, {"fault_tolerance"=>0, "name"=>"cinder", "node_ids"=>[]}, {"fault_tolerance"=>0, "name"=>"cinder-block-device", "node_ids"=>[]}, {"fault_tolerance"=>1, "name"=>"cinder-vmware", "node_ids"=>[]}, {"fault_tolerance"=>0, "name"=>"compute", "node_ids"=>["3", "2"]}, {"fault_tolerance"=>1, "name"=>"compute-vmware", "node_ids"=>[]}, {"fault_tolerance"=>1, "name"=>"mongo", "node_ids"=>[]}, {"fault_tolerance"=>1, "name"=>"primary-mongo", "node_ids"=>[]}, {"fault_tolerance"=>1, "name"=>"ceph-osd", "node_ids"=>["3", "2", "5", "4"]}, {"fault_tolerance"=>1, "name"=>"base-os", "node_ids"=>[]}, {"fault_tolerance"=>1, "name"=>"virt", "node_ids"=>[]}, {"fault_tolerance"=>1, "name"=>"ironic", "node_ids"=>[]} ] } end let(:tasks_graph) do {"1"=> [{ "type"=>"noop", "fail_on_error"=>true, "required_for"=>[], "requires"=> [], "id"=>"ironic_post_swift_key", "parameters"=>{}, }], "null"=> [{ "skipped"=>true, "type"=>"skipped", "fail_on_error"=>false, "required_for"=>[], "requires"=>[], "parameters"=>{}, "id"=>"post_deployment_start"}] } end let(:tasks_graph_2) do {"master"=> [{ "type"=>"noop", "fail_on_error"=>true, "required_for"=>[], "requires"=> [], "id"=>"ironic_post_swift_key", "parameters"=>{}, }], "null"=> [] } end let(:tasks_directory) do {"ironic_post_swift_key"=>{ "parameters"=>{ "retries"=>3, "cmd"=>"sh generate_keys.sh -i 1 -s 'ceph' -p /var/lib/fuel/keys/", "cwd"=>"/", "timeout"=>180, "interval"=>1}, "type"=>"shell", "id"=>"ironic_post_swift_key"}, "post_deployment_start"=>{ "parameters"=>{} } } end let(:task_deployment) { Astute::TaskDeployment.new(ctx) } describe '#deploy' do it 'should run deploy' do task_deployment.stubs(:fail_offline_nodes).returns([]) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) Astute::TaskCluster.any_instance.expects(:run).returns({:success => true}) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'should not raise error if deployment info not provided' do task_deployment.stubs(:fail_offline_nodes).returns([]) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) Astute::TaskCluster.any_instance.expects(:run).returns({:success => true}) expect{task_deployment.deploy( tasks_graph: tasks_graph, tasks_directory: tasks_directory)}.to_not raise_error end it 'should raise error if tasks graph not provided' do expect{task_deployment.deploy( tasks_directory: tasks_directory)}.to raise_error( Astute::DeploymentEngineError, "Deployment graph was not provided!" ) end it 'should support virtual node' do d_t = task_deployment.send(:support_virtual_node, tasks_graph) expect(d_t.keys).to include 'virtual_sync_node' expect(d_t.keys).not_to include 'null' end it 'should support critical nodes' do critical_nodes = task_deployment.send( :critical_node_uids, tasks_metadata['fault_tolerance_groups'] ) expect(critical_nodes).to include '1' expect(critical_nodes).to include '2' expect(critical_nodes).to include '3' expect(critical_nodes.size).to eql(3) end it 'should fail offline nodes' do Astute::TaskPreDeploymentActions.any_instance.stubs(:process) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) task_deployment.expects(:fail_offline_nodes).returns([]) Astute::TaskCluster.any_instance.stubs(:run).returns({:success => true}) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'should not fail if there are no nodes to check for offline nodes' do Astute::TaskPreDeploymentActions.any_instance.stubs(:process) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) task_deployment.expects(:fail_offline_nodes).returns([]) Astute::TaskCluster.any_instance.stubs(:run).returns({:success => true}) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph_2, tasks_directory: tasks_directory) end it 'should setup stop condition' do Astute::TaskPreDeploymentActions.any_instance.stubs(:process) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) task_deployment.stubs(:fail_offline_nodes).returns([]) Astute::TaskCluster.any_instance.stubs(:run).returns({:success => true}) Astute::TaskCluster.any_instance.expects(:stop_condition) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'should setup deployment logger' do Astute::TaskPreDeploymentActions.any_instance.stubs(:process) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) task_deployment.stubs(:fail_offline_nodes).returns([]) Astute::TaskCluster.any_instance.stubs(:run).returns({:success => true}) Deployment::Log.expects(:logger=).with(Astute.logger) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end context 'task concurrency' do let(:task_concurrency) { mock('task_concurrency') } before(:each) do task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) task_deployment.stubs(:fail_offline_nodes).returns([]) Astute::TaskCluster.any_instance.stubs(:run).returns({:success => true}) Deployment::Concurrency::Counter.any_instance .stubs(:maximum=).with( Astute.config.max_nodes_per_call) end it 'should setup 0 if no task concurrency setup' do Deployment::Concurrency::Counter.any_instance.expects(:maximum=).with(0).times(5) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'it should setup 1 if task concurrency type one_by_one' do tasks_graph['1'].first['parameters']['strategy'] = {'type' => 'one_by_one'} Deployment::Concurrency::Counter.any_instance.expects(:maximum=) .with(0).times(4) Deployment::Concurrency::Counter.any_instance.expects(:maximum=) .with(1) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'should setup task concurrency as amount if type is parallel' do tasks_graph['1'].first['parameters']['strategy'] = {'type' => 'parallel', 'amount' => 7} Deployment::Concurrency::Counter.any_instance.expects(:maximum=) .with(0).times(4) Deployment::Concurrency::Counter.any_instance.expects(:maximum=) .with(7) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'should setup 0 if task strategy is parallel and amount do not set' do tasks_graph['1'].first['parameters']['strategy'] = {'type' => 'parallel'} Deployment::Concurrency::Counter.any_instance.expects(:maximum=) .with(0).times(5) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'should raise error if amount is non-positive integer and type is parallel' do tasks_graph['1'].first['parameters']['strategy'] = {'type' => 'parallel', 'amount' => -4} Deployment::Concurrency::Counter.any_instance.expects(:maximum=) .with(0).times(2) expect {task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory)}.to raise_error( Astute::DeploymentEngineError, /expect only non-negative integer, but got -4./ ) end end context 'dry_run' do it 'should not run actual deployment if dry_run is set to True' do task_deployment.stubs(:fail_offline_nodes).returns([]) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) Astute::TaskCluster.any_instance.expects(:run).never task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory, dry_run: true) end end context 'noop_run' do it 'should run noop deployment without error states' do task_deployment.stubs(:fail_offline_nodes).returns([]) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) Astute::TaskCluster.any_instance.expects(:run).returns({:success => true}) task_deployment.deploy( tasks_metadata: tasks_metadata.merge({'noop_run' => true}), tasks_graph: tasks_graph, tasks_directory: tasks_directory) end end context 'config' do around(:each) do |example| max_nodes_old_value = Astute.config.max_nodes_per_call example.run Astute.config.max_nodes_per_call = max_nodes_old_value end it 'should setup max nodes per call using config' do Astute.config.max_nodes_per_call = 33 task_deployment.stubs(:fail_offline_nodes).returns([]) task_deployment.stubs(:write_graph_to_file) ctx.stubs(:report) Astute::TaskCluster.any_instance .stubs(:run) .returns({:success => true}) node_concurrency = mock('node_concurrency') Astute::TaskCluster.any_instance .expects(:node_concurrency).returns(node_concurrency) node_concurrency.expects(:maximum=).with(Astute.config.max_nodes_per_call) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end end context 'should report final status' do it 'succeed status' do Astute::TaskCluster.any_instance.stubs(:run).returns({:success => true}) task_deployment.stubs(:fail_offline_nodes).returns([]) task_deployment.stubs(:write_graph_to_file) ctx.expects(:report).with({'status' => 'ready', 'progress' => 100}) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'failed status' do failed_node = mock('node') failed_node.expects(:id).returns('1') failed_task = mock('task') failed_task.expects(:node).returns(failed_node) failed_task.expects(:name).returns('test') failed_task.expects(:status).returns(:failed) Astute::TaskCluster.any_instance.stubs(:run).returns({ :success => false, :failed_nodes => [failed_node], :failed_tasks => [failed_task], :status => 'Failed because of'}) task_deployment.stubs(:fail_offline_nodes).returns([]) task_deployment.stubs(:write_graph_to_file) ctx.expects(:report).with('nodes' => [{ 'uid' => '1', 'status' => 'error', 'error_type' => 'deploy', 'error_msg' => 'Failed because of', 'deployment_graph_task_name' => 'test', 'task_status' => 'failed' }]) ctx.expects(:report).with({ 'status' => 'error', 'progress' => 100, 'error' => 'Failed because of'}) task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end end context 'graph file' do around(:each) do |example| old_value = Astute.config.enable_graph_file example.run Astute.config.enable_graph_file = old_value end it 'should write if disable' do Astute.config.enable_graph_file = false task_deployment.stubs(:fail_offline_nodes).returns([]) ctx.stubs(:report) Astute::TaskCluster.any_instance.stubs(:run).returns({:success => true}) file_handle = mock file_handle.expects(:write).with(regexp_matches(/digraph/)).never File.expects(:open).with("#{Astute.config.graph_dot_dir}/graph-#{ctx.task_id}.dot", 'w') .yields(file_handle).never task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end it 'should write graph if enable' do Astute.config.enable_graph_file = true task_deployment.stubs(:fail_offline_nodes).returns([]) ctx.stubs(:report) Astute::TaskCluster.any_instance.stubs(:run).returns({:success => true}) file_handle = mock file_handle.expects(:write).with(regexp_matches(/digraph/)).once File.expects(:open).with("#{Astute.config.graph_dot_dir}/graph-#{ctx.task_id}.dot", 'w') .yields(file_handle).once task_deployment.deploy( tasks_metadata: tasks_metadata, tasks_graph: tasks_graph, tasks_directory: tasks_directory) end end # 'graph file' end end