diff --git a/app/models/infra_conversion_job.rb b/app/models/infra_conversion_job.rb index 8160ab1690f..3d7391b43ce 100644 --- a/app/models/infra_conversion_job.rb +++ b/app/models/infra_conversion_job.rb @@ -42,6 +42,12 @@ def load_transitions } end + # Example state: + # :state_name => { + # :description => 'State description', + # :weight => 30, + # :max_retries => 960 + # } def state_settings @state_settings ||= { :running_in_automate => { @@ -55,6 +61,49 @@ def migration_task # valid states: %w(migrate pending finished active queued) end + def on_entry(state_hash, _) + state_hash || { + :state => 'active', + :status => 'Ok', + :description => state_settings[state.to_sym][:description], + :started_on => Time.now.utc, + :percent => 0.0 + }.compact + end + + def on_retry(state_hash, state_progress = nil) + if state_progress.nil? + state_hash[:percent] = context["retries_#{state}".to_sym].to_f / state_settings[state.to_sym][:max_retries].to_f * 100.0 + else + state_hash.merge!(state_progress) + end + state_hash[:updated_on] = Time.now.utc + state_hash + end + + def on_exit(state_hash, _) + state_hash[:state] = 'finished' + state_hash[:percent] = 100.0 + state_hash[:updated_on] = Time.now.utc + state_hash + end + + def on_error(state_hash, _) + state_hash[:state] = 'finished' + state_hash[:status] = 'Error' + state_hash[:updated_on] = Time.now.utc + state_hash + end + + def update_migration_task_progress(state_phase, state_progress = nil) + progress = migration_task.options[:progress] || { :current_state => state, :percent => 0.0, :states => {} } + state_hash = send(state_phase, progress[:states][state.to_sym], state_progress) + progress[:states][state.to_sym] = state_hash + progress[:current_description] = state_settings[state.to_sym][:description] if state_phase == :on_entry && state_settings[state.to_sym][:description].present? + progress[:percent] += state_hash[:percent] * state_settings[state.to_sym][:weight] / 100.0 if state_settings[state.to_sym][:weight].present? + migration_task.update_transformation_progress(progress) + end + # Temporary method to allow switching from InfraConversionJob to Automate. # In Automate, another method waits for workflow_runner to be 'automate'. def handover_to_automate @@ -94,6 +143,8 @@ def prep_message(contents) # --- Methods that implement the state machine transitions --- # + # This transition simply allows to officially mark the task as migrating. + # Temporarily, it also hands over to Automate. def start migration_task.update!(:state => 'migrate') handover_to_automate diff --git a/spec/models/infra_conversion_job_spec.rb b/spec/models/infra_conversion_job_spec.rb index c514227af0a..adb2a58feb8 100644 --- a/spec/models/infra_conversion_job_spec.rb +++ b/spec/models/infra_conversion_job_spec.rb @@ -12,6 +12,254 @@ end end + context 'state hash methods' do + before do + job.state = 'running_in_automate' + job.context[:retries_running_in_automate] = 1728 + end + + context '.on_entry' do + it 'initializes the state hash if it did not exist' do + Timecop.freeze(2019, 2, 6) do + expect(job.on_entry(nil, nil)).to eq( + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc, + :percent => 0.0 + ) + end + end + end + + context '.on_retry' do + it 'uses ad-hoc percentage if no progress is provided' do + Timecop.freeze(2019, 2, 6) do + state_hash = { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 10.0 + } + state_hash_diff = { + :percent => 20.0, + :updated_on => Time.now.utc + } + expect(job.on_retry(state_hash, nil)).to eq(state_hash.merge(state_hash_diff)) + end + end + + it 'uses percentage from progress hash' do + Timecop.freeze(2019, 2, 6) do + state_hash = { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 10.0 + } + state_hash_diff = { + :percent => 25.0, + :updated_on => Time.now.utc + } + expect(job.on_retry(state_hash, :percent => 25.0)).to eq(state_hash.merge(state_hash_diff)) + end + end + end + + context '.on_exit' do + it 'uses percentage from progress hash' do + Timecop.freeze(2019, 2, 6) do + state_hash = { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 80.0 + } + state_hash_diff = { + :state => 'finished', + :percent => 100.0, + :updated_on => Time.now.utc + } + expect(job.on_exit(state_hash, nil)).to eq(state_hash.merge(state_hash_diff)) + end + end + end + + context '.on_error' do + it 'uses percentage from progress hash' do + Timecop.freeze(2019, 2, 6) do + state_hash = { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 80.0 + } + state_hash_diff = { + :state => 'finished', + :status => 'Error', + :updated_on => Time.now.utc + } + expect(job.on_error(state_hash, nil)).to eq(state_hash.merge(state_hash_diff)) + end + end + end + + context '.update_migration_task_progress' do + it 'initializes the progress hash on entry if it does not exist' do + Timecop.freeze(2019, 2, 6) do + job.update_migration_task_progress(:on_entry, nil) + expect(task.reload.options[:progress]).to eq( + :current_state => 'running_in_automate', + :percent => 0.0, + :states => { + :running_in_automate => { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc, + :percent => 0.0 + } + } + ) + end + end + + it 'updates the task progress hash on retry without a state progress hash' do + job.context[:retries_running_in_automate] = 1728 + Timecop.freeze(2019, 2, 6) do + progress = { + :current_state => 'running_in_automate', + :percent => 10.0, + :states => { + :running_in_automate => { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 10.0, + :updated_on => Time.now.utc - 30.seconds + } + } + } + task.update_options(:progress => progress) + job.update_migration_task_progress(:on_retry, nil) + expect(task.reload.options[:progress]).to eq( + :current_state => 'running_in_automate', + :percent => 10.0, + :states => { + :running_in_automate => { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 20.0, + :updated_on => Time.now.utc + } + } + ) + end + end + + it 'updates the task progress hash on retry with a state progress hash' do + job.context[:retries_running_in_automate] = 1728 + Timecop.freeze(2019, 2, 6) do + progress = { + :current_state => 'running_in_automate', + :percent => 10.0, + :states => { + :running_in_automate => { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 10.0, + :updated_on => Time.now.utc - 30.seconds + } + } + } + task.update_options(:progress => progress) + job.update_migration_task_progress(:on_retry, :percent => 30) + expect(task.reload.options[:progress]).to eq( + :current_state => 'running_in_automate', + :percent => 10.0, + :states => { + :running_in_automate => { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 30.0, + :updated_on => Time.now.utc + } + } + ) + end + end + + it 'updates the task progress hash on exit' do + job.context[:retries_running_in_automate] = 1728 + Timecop.freeze(2019, 2, 6) do + progress = { + :current_state => 'running_in_automate', + :percent => 10.0, + :states => { + :running_in_automate => { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 10.0, + :updated_on => Time.now.utc - 30.seconds + } + } + } + task.update_options(:progress => progress) + job.update_migration_task_progress(:on_exit, nil) + expect(task.reload.options[:progress]).to eq( + :current_state => 'running_in_automate', + :percent => 10.0, + :states => { + :running_in_automate => { + :state => 'finished', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 100.0, + :updated_on => Time.now.utc + } + } + ) + end + end + + it 'updates the task progress hash on error' do + job.context[:retries_running_in_automate] = 1728 + Timecop.freeze(2019, 2, 6) do + progress = { + :current_state => 'running_in_automate', + :percent => 10.0, + :states => { + :running_in_automate => { + :state => 'active', + :status => 'Ok', + :started_on => Time.now.utc - 1.minute, + :percent => 10.0, + :updated_on => Time.now.utc - 30.seconds + } + } + } + task.update_options(:progress => progress) + job.update_migration_task_progress(:on_error, nil) + expect(task.reload.options[:progress]).to eq( + :current_state => 'running_in_automate', + :percent => 10.0, + :states => { + :running_in_automate => { + :state => 'finished', + :status => 'Error', + :started_on => Time.now.utc - 1.minute, + :percent => 10.0, + :updated_on => Time.now.utc + } + } + ) + end + end + end + end + context 'state transitions' do %w[start poll_automate_state_machine finish abort_job cancel error].each do |signal| shared_examples_for "allows #{signal} signal" do @@ -73,7 +321,7 @@ end end - context 'operations' do + context 'transition methods' do let(:poll_interval) { Settings.transformation.job.retry_interval } context '#start' do