Commit 5b4bd7a5 authored by Aakriti Gupta's avatar Aakriti Gupta Committed by Michael Kozono

Geo: Refactor verification related concerns

- Create VerifiableModel concern to break down
VerificationState concern such that
VerifiableModel holds common code to be used for
verification on both primary and secondary, which
VerificationState is specifically for checksumming on primary.
parent 356491bd
......@@ -175,8 +175,8 @@ That's all of the required database changes.
#### Step 1. Implement replication and verification
- [ ] Add the following lines to the `cool_widget` model to accomplish some important tasks:
- Include `Gitlab::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
- Include the `::Gitlab::Geo::VerificationState` concern.
- Include `::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
- Include the `::Geo::VerifiableModel` concern.
- Delegate verification related methods to the `cool_widget_state` model.
- For verification, override some scopes to use the `cool_widget_states` table instead of the model table.
- Implement the `verification_state_object` method to return the object that holds
......@@ -192,8 +192,8 @@ That's all of the required database changes.
class CoolWidget < ApplicationRecord
...
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator Geo::CoolWidgetReplicator
......
......@@ -179,8 +179,8 @@ That's all of the required database changes.
#### Step 1. Implement replication and verification
- [ ] Add the following lines to the `cool_widget` model to accomplish some important tasks:
- Include `Gitlab::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
- Include the `::Gitlab::Geo::VerificationState` concern.
- Include `::Geo::ReplicableModel` in the `CoolWidget` class, and specify the Replicator class `with_replicator Geo::CoolWidgetReplicator`.
- Include the `::Geo::VerifiableModel` concern.
- Delegate verification related methods to the `cool_widget_state` model.
- For verification, override some scopes to use the `cool_widget_states` table instead of the model table.
- Implement the `verification_state_object` method to return the object that holds
......@@ -194,8 +194,8 @@ That's all of the required database changes.
class CoolWidget < ApplicationRecord
...
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator Geo::CoolWidgetReplicator
......
......@@ -117,7 +117,7 @@ the model code:
```ruby
class Packages::PackageFile < ApplicationRecord
include ::Gitlab::Geo::ReplicableModel
include ::Geo::ReplicableModel
with_replicator Geo::PackageFileReplicator
end
......
# frozen_string_literal: true
module Geo
module ReplicableModel
extend ActiveSupport::Concern
include Checksummable
included do
# If this hook turns out not to apply to all Models, perhaps we should extract a `ReplicableBlobModel`
after_create_commit -> { replicator.handle_after_create_commit if replicator.respond_to?(:handle_after_create_commit) }
after_destroy -> { replicator.handle_after_destroy if replicator.respond_to?(:handle_after_destroy) }
# Temporarily defining `verification_succeeded` and
# `verification_failed` for unverified models while verification is
# under development to avoid breaking GeoNodeStatusCheck code.
# TODO: Remove these after including `::Geo::VerificationState` on
# all models. https://gitlab.com/gitlab-org/gitlab/-/issues/280768
scope :verification_succeeded, -> { none }
scope :verification_failed, -> { none }
# These scopes are intended to be overridden as needed
scope :available_replicables, -> { all }
# On primary, `verifiables` are records that can be checksummed and/or are replicable.
# On secondary, `verifiables` are records that have already been replicated
# and (ideally) have been checksummed on the primary
scope :verifiables, -> { self.respond_to?(:with_files_stored_locally) ? available_replicables.with_files_stored_locally : available_replicables }
# When storing verification details in the same table as the model,
# the scope `available_verifiables` returns only those records
# that are eligible for verification, i.e. the same as the scope
# `verifiables`.
# When using a separate table to store verification details,
# the scope `available_verifiables` should return all records
# from the separate table because the separate table will
# always only have records corresponding to replicables that are verifiable.
# For this, override the scope in the replicable model, e.g. like so in
# `MergeRequestDiff`,
# `scope :available_verifiables, -> { joins(:merge_request_diff_detail) }`
scope :available_verifiables, -> { verifiables }
end
class_methods do
# Associate current model with specified replicator
#
# @param [Gitlab::Geo::Replicator] klass
def with_replicator(klass)
raise ArgumentError, 'Must be a class inheriting from Gitlab::Geo::Replicator' unless klass < ::Gitlab::Geo::Replicator
class_eval <<-RUBY, __FILE__, __LINE__ + 1
define_method :replicator do
@_replicator ||= klass.new(model_record: self)
end
define_singleton_method :replicator_class do
@_replicator_class ||= klass
end
RUBY
end
end
# Geo Replicator
#
# @abstract
# @return [Gitlab::Geo::Replicator]
def replicator
raise NotImplementedError, 'There is no Replicator defined for this model'
end
def in_replicables_for_current_secondary?
self.class.replicables_for_current_secondary(self).exists?
end
end
end
# frozen_string_literal: true
module Geo
# This concern is included on Model classes (as opposed to Registry classes)
# to manage their verification states. Note that this concern does not handle
# how verification is performed; see `VerifiableReplicator`.
#
# It handles both cases where verification state is stored in a separate
# table or when it is stored in the same table as the model.
module VerifiableModel
extend ActiveSupport::Concern
include ::Geo::VerificationState
included do
def save_verification_details
return unless self.class.separate_verification_state_table?
return unless self.class.verifiables.primary_key_in(self).exists?
# During a transaction, `verification_state_object` could be built before
# a value for `verification_state_model_key` exists. So we check for that
# before saving the `verification_state_object`
unless verification_state_object.persisted?
verification_state_object[self.class.verification_state_model_key] = self.id
end
verification_state_object.save!
end
# Implement this method in the class that includes this concern to specify
# a different ActiveRecord association name that stores the verification state
# See module EE::MergeRequestDiff for example
def verification_state_object
raise NotImplementedError if self.class.separate_verification_state_table?
self
end
end
class_methods do
include Delay
def pluck_verification_details_ids_in_range(range)
verification_state_table_class
.where(self.verification_state_model_key => range)
.pluck(self.verification_state_model_key)
end
def pluck_verifiable_ids_in_range(range)
self
.verifiables
.primary_key_in(range)
.pluck_primary_key
end
# @return whether primary checksum data is stored in a table separate
# from the model table
def separate_verification_state_table?
verification_state_table_name != table_name
end
end
end
end
......@@ -4,13 +4,13 @@ module Geo
module VerifiableRegistry
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
include ::Gitlab::Geo::VerificationState
include ::Geo::VerificationState
class_methods do
extend ::Gitlab::Utils::Override
# Overrides a method in `Gitlab::Geo::VerificationState`. This method is
# used by `Gitlab::Geo::VerificationState.start_verification_batch` to
# Overrides a method in `::Geo::VerificationState`. This method is
# used by `::Geo::VerificationState.start_verification_batch` to
# produce a query which must return values of the primary key of the
# *model*, not of the *registry*. We need this so we can instantiate
# Replicators.
......
This diff is collapsed.
......@@ -6,8 +6,8 @@ module EE
extend ActiveSupport::Concern
prepended do
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator ::Geo::PipelineArtifactReplicator
end
......
......@@ -11,8 +11,8 @@ module EE
STORE_COLUMN = :file_store
prepended do
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator ::Geo::LfsObjectReplicator
......
......@@ -5,9 +5,9 @@ module EE
extend ActiveSupport::Concern
prepended do
include ::Gitlab::Geo::ReplicableModel
include ::Geo::ReplicableModel
include ObjectStorable
include ::Gitlab::Geo::VerificationState
include ::Geo::VerifiableModel
STORE_COLUMN = :external_diff_store
......
......@@ -6,8 +6,8 @@ module EE
extend ActiveSupport::Concern
prepended do
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator ::Geo::PackageFileReplicator
end
......
......@@ -5,8 +5,8 @@ module EE
extend ActiveSupport::Concern
prepended do
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator ::Geo::PagesDeploymentReplicator
......
......@@ -5,8 +5,8 @@ module EE
extend ActiveSupport::Concern
prepended do
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
include FromUnion
with_replicator ::Geo::SnippetRepositoryReplicator
......
......@@ -6,8 +6,8 @@ module EE
extend ActiveSupport::Concern
prepended do
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator ::Geo::TerraformStateVersionReplicator
......
......@@ -10,8 +10,8 @@ module EE
prepended do
include ::Gitlab::SQL::Pattern
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator ::Geo::UploadReplicator
......
# frozen_string_literal: true
class GroupWikiRepository < ApplicationRecord
include ::Gitlab::Geo::ReplicableModel
include ::Geo::ReplicableModel
include EachBatch
include Shardable
......
# frozen_string_literal: true
module Gitlab
module Geo
module ReplicableModel
extend ActiveSupport::Concern
include Checksummable
included do
# If this hook turns out not to apply to all Models, perhaps we should extract a `ReplicableBlobModel`
after_create_commit -> { replicator.handle_after_create_commit if replicator.respond_to?(:handle_after_create_commit) }
after_destroy -> { replicator.handle_after_destroy if replicator.respond_to?(:handle_after_destroy) }
# Temporarily defining `verification_succeeded` and
# `verification_failed` for unverified models while verification is
# under development to avoid breaking GeoNodeStatusCheck code.
# TODO: Remove these after including `Gitlab::Geo::VerificationState` on
# all models. https://gitlab.com/gitlab-org/gitlab/-/issues/280768
scope :verification_succeeded, -> { none }
scope :verification_failed, -> { none }
# These scopes are intended to be overridden as needed
scope :available_replicables, -> { all }
# On primary, `verifiables` are records that can be checksummed and/or are replicable.
# On secondary, `verifiables` are records that have already been replicated
# and (ideally) have been checksummed on the primary
scope :verifiables, -> { self.respond_to?(:with_files_stored_locally) ? available_replicables.with_files_stored_locally : available_replicables }
# When storing verification details in the same table as the model,
# the scope `available_verifiables` returns only those records
# that are eligible for verification, i.e. the same as the scope
# `verifiables`.
# When using a separate table to store verification details,
# the scope `available_verifiables` should return all records
# from the separate table because the separate table will
# always only have records corresponding to replicables that are verifiable.
# For this, override the scope in the replicable model, e.g. like so in
# `MergeRequestDiff`,
# `scope :available_verifiables, -> { joins(:merge_request_diff_detail) }`
scope :available_verifiables, -> { verifiables }
end
class_methods do
# Associate current model with specified replicator
#
# @param [Gitlab::Geo::Replicator] klass
def with_replicator(klass)
raise ArgumentError, 'Must be a class inheriting from Gitlab::Geo::Replicator' unless klass < ::Gitlab::Geo::Replicator
class_eval <<-RUBY, __FILE__, __LINE__ + 1
define_method :replicator do
@_replicator ||= klass.new(model_record: self)
end
define_singleton_method :replicator_class do
@_replicator_class ||= klass
end
RUBY
end
end
# Geo Replicator
#
# @abstract
# @return [Gitlab::Geo::Replicator]
def replicator
raise NotImplementedError, 'There is no Replicator defined for this model'
end
def in_replicables_for_current_secondary?
self.class.replicables_for_current_secondary(self).exists?
end
end
end
end
This diff is collapsed.
......@@ -8,7 +8,7 @@ require 'spec_helper'
# against a DummyModel.
# - Place tests in replicable_model_shared_examples.rb if you want them to be
# run against every real Model.
RSpec.describe Gitlab::Geo::ReplicableModel do
RSpec.describe Geo::ReplicableModel do
include ::EE::GeoHelpers
let_it_be(:primary_node) { create(:geo_node, :primary) }
......@@ -42,6 +42,17 @@ RSpec.describe Gitlab::Geo::ReplicableModel do
it 'instantiates a replicator into the model' do
expect(subject.replicator).to be_a(Geo::DummyReplicator)
end
context 'when replicator is not defined in inheriting class' do
before do
stub_const('DummyModel', Class.new(ApplicationRecord))
DummyModel.class_eval { include ::Geo::ReplicableModel }
end
it 'raises NotImplementedError' do
expect { DummyModel.new.replicator }.to raise_error(NotImplementedError)
end
end
end
describe '#in_replicables_for_current_secondary?' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::VerifiableModel do
include ::EE::GeoHelpers
context 'when separate table is used for verification state' do
before(:all) do
create_dummy_model_with_separate_state_table
end
after(:all) do
drop_dummy_model_with_separate_state_table
end
before do
stub_dummy_replicator_class(model_class: 'TestDummyModelWithSeparateState')
stub_dummy_model_with_separate_state_class
end
subject { TestDummyModelWithSeparateState.new }
describe '.verification_state_model_key' do
it 'returns the primary key of the state model' do
expect(subject.class.verification_state_model_key).to eq(TestDummyModelState.primary_key)
end
end
end
context 'when separate table is not used for verification state' do
before(:all) do
create_dummy_model_table
end
after(:all) do
drop_dummy_model_table
end
before do
stub_dummy_replicator_class
stub_dummy_model_class
end
subject { DummyModel.new }
describe '.verification_state_object' do
it 'returns self' do
expect(subject.verification_state_object.id).to eq(subject.id)
end
end
describe '.verification_state_model_key' do
it 'returns the primary key of the model' do
expect(subject.class.verification_state_model_key).to eq(DummyModel.primary_key)
end
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Geo::VerificationState do
RSpec.describe Geo::VerificationState do
include ::EE::GeoHelpers
let_it_be(:primary_node) { create(:geo_node, :primary) }
......@@ -434,7 +434,7 @@ RSpec.describe Gitlab::Geo::VerificationState do
end
before do
stub_dummy_replicator_class(model_class: 'DummyModelWithSeparateState')
stub_dummy_replicator_class(model_class: 'TestDummyModelWithSeparateState')
stub_dummy_model_with_separate_state_class
end
......
......@@ -79,8 +79,8 @@ module EE
stub_const('DummyModel', Class.new(ApplicationRecord))
DummyModel.class_eval do
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator Geo::DummyReplicator
......@@ -170,8 +170,8 @@ module EE
TestDummyModelWithSeparateState.class_eval do
self.table_name = '_test_dummy_model_with_separate_states'
include ::Gitlab::Geo::ReplicableModel
include ::Gitlab::Geo::VerificationState
include ::Geo::ReplicableModel
include ::Geo::VerifiableModel
with_replicator Geo::DummyReplicator
......
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