Commit ef538e9f authored by Mike Kozono's avatar Mike Kozono

Leverage state transitions

And add two convenience methods for setting a value at the same time as
calling a transition:

- verification_succeeded_with_checksum!
- verification_failed_with_message!
parent 672545b3
...@@ -52,10 +52,9 @@ module Geo ...@@ -52,10 +52,9 @@ module Geo
def verify def verify
checksum = model_record.calculate_checksum checksum = model_record.calculate_checksum
model_record.update_verification_state!(checksum: checksum) model_record.verification_succeeded_with_checksum!(checksum)
rescue => e rescue => e
log_error('Error calculating the checksum', e) model_record.verification_failed_with_message!('Error calculating the checksum', e)
model_record.update_verification_state!(failure: e.message)
end end
# Check if given checksum matches known one # Check if given checksum matches known one
......
...@@ -13,6 +13,7 @@ module Gitlab ...@@ -13,6 +13,7 @@ module Gitlab
extend ActiveSupport::Concern extend ActiveSupport::Concern
include ::ShaAttribute include ::ShaAttribute
include Delay include Delay
include Gitlab::Geo::LogHelpers
VERIFICATION_STATE_VALUES = { VERIFICATION_STATE_VALUES = {
verification_pending: 0, verification_pending: 0,
...@@ -41,7 +42,9 @@ module Gitlab ...@@ -41,7 +42,9 @@ module Gitlab
state_machine :verification_state, initial: :verification_pending do state_machine :verification_state, initial: :verification_pending do
state :verification_pending, value: VERIFICATION_STATE_VALUES[:verification_pending] state :verification_pending, value: VERIFICATION_STATE_VALUES[:verification_pending]
state :verification_started, value: VERIFICATION_STATE_VALUES[:verification_started] state :verification_started, value: VERIFICATION_STATE_VALUES[:verification_started]
state :verification_succeeded, value: VERIFICATION_STATE_VALUES[:verification_succeeded] state :verification_succeeded, value: VERIFICATION_STATE_VALUES[:verification_succeeded] do
validates :verification_checksum, presence: true
end
state :verification_failed, value: VERIFICATION_STATE_VALUES[:verification_failed] do state :verification_failed, value: VERIFICATION_STATE_VALUES[:verification_failed] do
validates :verification_failure, presence: true validates :verification_failure, presence: true
end end
...@@ -58,14 +61,18 @@ module Gitlab ...@@ -58,14 +61,18 @@ module Gitlab
end end
before_transition any => :verification_failed do |instance, _| before_transition any => :verification_failed do |instance, _|
instance.verification_checksum = nil
instance.verification_retry_count ||= 0
instance.verification_retry_count += 1 instance.verification_retry_count += 1
instance.verification_retry_at = instance.next_retry_time(instance.verification_retry_count) instance.verification_retry_at = instance.next_retry_time(instance.verification_retry_count)
instance.verified_at = Time.current
end end
before_transition any => :verification_succeeded do |instance, _| before_transition any => :verification_succeeded do |instance, _|
instance.verification_retry_count = 0 instance.verification_retry_count = 0
instance.verification_retry_at = nil instance.verification_retry_at = nil
instance.verification_failure = nil instance.verification_failure = nil
instance.verified_at = Time.current
end end
event :verification_started do event :verification_started do
...@@ -92,32 +99,27 @@ module Gitlab ...@@ -92,32 +99,27 @@ module Gitlab
end end
end end
# Update checksum on Geo primary database # Convenience method to update checksum and transition to success state.
# #
# @param [String] checksum value generated by the checksum routine # @param [String] checksum value generated by the checksum routine
# @param [String] failure (optional) stacktrace from failed execution def verification_succeeded_with_checksum!(checksum)
def update_verification_state!(checksum: nil, failure: nil) self.verification_checksum = checksum
retry_at, retry_count = calculate_next_retry_attempt if failure.present?
update!(
verification_checksum: checksum,
verified_at: Time.current,
verification_failure: failure,
verification_retry_at: retry_at,
verification_retry_count: retry_count
)
end
def calculate_next_retry_attempt self.verification_succeeded!
retry_count = model_record.verification_retry_count.to_i + 1
[next_retry_time(retry_count), retry_count]
end end
# Convenience method to update failure message and transition to failed
# state.
#
# @param [String] message error information # @param [String] message error information
# @param [StandardError] error exception # @param [StandardError] error exception
def set_verification_failure(message, error = nil) def verification_failed_with_message!(message, error = nil)
log_error('Error calculating the checksum', error)
self.verification_failure = message self.verification_failure = message
self.verification_failure += ": #{error.message}" if error.respond_to?(:message) self.verification_failure += ": #{error.message}" if error.respond_to?(:message)
self.verification_failed!
end end
end end
end end
......
...@@ -19,26 +19,29 @@ RSpec.describe Gitlab::Geo::VerificationState do ...@@ -19,26 +19,29 @@ RSpec.describe Gitlab::Geo::VerificationState do
before do before do
stub_dummy_replicator_class stub_dummy_replicator_class
stub_dummy_model_class stub_dummy_model_class
subject.verification_started
subject.save!
end end
subject { DummyModel.new } subject { DummyModel.new }
describe '#update_verification_state!' do describe '#verification_succeeded_with_checksum!' do
before do
subject.save!
end
it 'saves the checksum' do it 'saves the checksum' do
subject.update_verification_state!(checksum: 'abc123') subject.verification_succeeded_with_checksum!('abc123')
expect(subject.reload.verification_checksum).to eq('abc123') expect(subject.reload.verification_checksum).to eq('abc123')
expect(subject.verified_at).not_to be_nil expect(subject.verified_at).not_to be_nil
end end
end
describe '#verification_failed_with_message!' do
it 'saves the error message and increments retry counter' do it 'saves the error message and increments retry counter' do
subject.update_verification_state!(failure: 'Failure to calculate checksum') error = double('error', message: 'An error message')
subject.verification_failed_with_message!('Failure to calculate checksum', error)
expect(subject.reload.verification_failure).to eq 'Failure to calculate checksum' expect(subject.reload.verification_failure).to eq 'Failure to calculate checksum: An error message'
expect(subject.verification_retry_count).to be 1 expect(subject.verification_retry_count).to be 1
end end
end end
......
...@@ -80,6 +80,7 @@ module EE ...@@ -80,6 +80,7 @@ module EE
DummyModel.class_eval do DummyModel.class_eval do
include ::Gitlab::Geo::ReplicableModel include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
with_replicator Geo::DummyReplicator with_replicator Geo::DummyReplicator
...@@ -104,6 +105,12 @@ module EE ...@@ -104,6 +105,12 @@ module EE
ActiveRecord::Schema.define do ActiveRecord::Schema.define do
create_table :dummy_models, force: true do |t| create_table :dummy_models, force: true do |t|
t.binary :verification_checksum t.binary :verification_checksum
t.integer :verification_state
t.datetime_with_timezone :verification_started_at
t.datetime_with_timezone :verified_at
t.datetime_with_timezone :verification_retry_at
t.integer :verification_retry_count
t.text :verification_failure
end end
end end
end end
......
...@@ -124,26 +124,40 @@ RSpec.shared_examples 'a verifiable replicator' do ...@@ -124,26 +124,40 @@ RSpec.shared_examples 'a verifiable replicator' do
describe '#verify' do describe '#verify' do
before do before do
model_record.verification_started
model_record.save! model_record.save!
end end
context 'when a Geo primary' do context 'on a Geo primary' do
context 'when an error is raised during calculate_checksum!' do before do
it 'delegates checksum calculation and the state change to model_record' do stub_primary_node
expect(model_record).to receive(:calculate_checksum).and_return('abc123') end
expect(model_record).to receive(:update_verification_state!).with(checksum: 'abc123')
replicator.verify context 'when verification was started' do
before do
model_record.verification_started!
end end
it 'passes the error message' do context 'when the checksum succeeds' do
allow(model_record).to receive(:calculate_checksum) do it 'delegates checksum calculation and the state change to model_record' do
raise StandardError.new('Failure to calculate checksum') expect(model_record).to receive(:calculate_checksum).and_return('abc123')
expect(model_record).to receive(:verification_succeeded_with_checksum!).with('abc123')
replicator.verify
end end
end
context 'when an error is raised during calculate_checksum' do
it 'passes the error message' do
error = StandardError.new('Some exception')
allow(model_record).to receive(:calculate_checksum) do
raise error
end
expect(model_record).to receive(:update_verification_state!).with(failure: 'Failure to calculate checksum') expect(model_record).to receive(:verification_failed_with_message!).with('Error calculating the checksum', error)
replicator.verify replicator.verify
end
end end
end end
end end
......
...@@ -21,7 +21,7 @@ RSpec.describe Geo::VerificationWorker, :geo do ...@@ -21,7 +21,7 @@ RSpec.describe Geo::VerificationWorker, :geo do
context 'when on a primary node' do context 'when on a primary node' do
before do before do
stub_primary_node stub_primary_node
package_file.update!(verification_checksum: nil) package_file.verification_started!
end end
it_behaves_like 'an idempotent worker' do it_behaves_like 'an idempotent worker' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment