Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/geo/framework.md')
-rw-r--r--doc/development/geo/framework.md876
1 files changed, 6 insertions, 870 deletions
diff --git a/doc/development/geo/framework.md b/doc/development/geo/framework.md
index 055b9ce4ad6..2b3e4826de5 100644
--- a/doc/development/geo/framework.md
+++ b/doc/development/geo/framework.md
@@ -160,880 +160,16 @@ the Geo team if you are unsure.
### Blob Replicator Strategy
-Models that use
-[CarrierWave's](https://github.com/carrierwaveuploader/carrierwave) `Uploader::Base`
-can be easily supported by Geo with the `Geo::BlobReplicatorStrategy` module.
+Models that use [CarrierWave's](https://github.com/carrierwaveuploader/carrierwave) `Uploader::Base` are supported by Geo with the `Geo::BlobReplicatorStrategy` module. For example, see how [Geo replication was implemented for Pipeline Artifacts](https://gitlab.com/gitlab-org/gitlab/-/issues/238464).
-First, each file should have its own primary ID and model. Geo strongly
-recommends treating *every single file* as a first-class citizen, because in
-our experience this greatly simplifies tracking replication and verification
-state.
+Each file is expected to have its own primary ID and model. Geo strongly recommends treating *every single file* as a first-class citizen, because in our experience this greatly simplifies tracking replication and verification state.
-For example, to add support for files referenced by a `Widget` model with a
-`widgets` table, you would perform the following steps:
-
-#### Replication
-
-1. Include `Gitlab::Geo::ReplicableModel` in the `Widget` class, and specify
- the Replicator class `with_replicator Geo::WidgetReplicator`.
-
- At this point the `Widget` class should look like this:
-
- ```ruby
- # frozen_string_literal: true
-
- class Widget < ApplicationRecord
- include ::Gitlab::Geo::ReplicableModel
-
- with_replicator Geo::WidgetReplicator
-
- mount_uploader :file, WidgetUploader
-
- # @param primary_key_in [Range, Widget] arg to pass to primary_key_in scope
- # @return [ActiveRecord::Relation<Widget>] everything that should be synced to this node, restricted by primary key
- def self.replicables_for_current_secondary(primary_key_in)
- # Should be implemented. The idea of the method is to restrict
- # the set of synced items depending on synchronization settings
- end
- ...
- end
- ```
-
- If there is a common constraint for records to be available for replication,
- make sure to also overwrite the `available_replicables` scope.
-
-1. Create `ee/app/replicators/geo/widget_replicator.rb`. Implement the
- `#carrierwave_uploader` method which should return a `CarrierWave::Uploader`,
- and implement the class method `.model` to return the `Widget` class:
-
- ```ruby
- # frozen_string_literal: true
-
- module Geo
- class WidgetReplicator < Gitlab::Geo::Replicator
- include ::Geo::BlobReplicatorStrategy
-
- def self.model
- ::Widget
- end
-
- def carrierwave_uploader
- model_record.file
- end
-
- # The feature flag follows the format `geo_#{replicable_name}_replication`,
- # so here it would be `geo_widget_replication`
- def self.replication_enabled_by_default?
- false
- end
- end
- end
- ```
-
-1. Add this replicator class to the method `replicator_classes` in
- `ee/lib/gitlab/geo.rb`:
-
- ```ruby
- REPLICATOR_CLASSES = [
- ::Geo::PackageFileReplicator,
- ::Geo::WidgetReplicator
- ]
- end
- ```
-
-1. Create `ee/spec/replicators/geo/widget_replicator_spec.rb` and perform
- the necessary setup to define the `model_record` variable for the shared
- examples:
-
- ```ruby
- # frozen_string_literal: true
-
- require 'spec_helper'
-
- RSpec.describe Geo::WidgetReplicator do
- let(:model_record) { build(:widget) }
-
- it_behaves_like 'a blob replicator'
- end
- ```
-
-1. Create the `widget_registry` table, with columns ordered according to [our guidelines](../ordering_table_columns.md) so Geo secondaries can track the sync and
- verification state of each Widget's file. This migration belongs in `ee/db/geo/migrate`:
-
- ```ruby
- # frozen_string_literal: true
-
- class CreateWidgetRegistry < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- disable_ddl_transaction!
-
- def up
- unless table_exists?(:widget_registry)
- ActiveRecord::Base.transaction do
- create_table :widget_registry, id: :bigserial, force: :cascade do |t|
- t.bigint :widget_id, null: false
- t.datetime_with_timezone :created_at, null: false
- t.datetime_with_timezone :last_synced_at
- t.datetime_with_timezone :retry_at
- t.datetime_with_timezone :verified_at
- t.datetime_with_timezone :verification_started_at
- t.datetime_with_timezone :verification_retry_at
- t.integer :state, default: 0, null: false, limit: 2
- t.integer :verification_state, default: 0, null: false, limit: 2
- t.integer :retry_count, default: 0, limit: 2
- t.integer :verification_retry_count, default: 0, limit: 2
- t.boolean :checksum_mismatch
- t.binary :verification_checksum
- t.binary :verification_checksum_mismatched
- t.string :verification_failure, limit: 255
- t.string :last_sync_failure, limit: 255
-
- t.index :widget_id, name: :index_widget_registry_on_widget_id, unique: true
- t.index :retry_at
- t.index :state
- # To optimize performance of WidgetRegistry.verification_failed_batch
- t.index :verification_retry_at, name: :widget_registry_failed_verification, order: "NULLS FIRST", where: "((state = 2) AND (verification_state = 3))"
- # To optimize performance of WidgetRegistry.needs_verification_count
- t.index :verification_state, name: :widget_registry_needs_verification, where: "((state = 2) AND (verification_state = ANY (ARRAY[0, 3])))"
- # To optimize performance of WidgetRegistry.verification_pending_batch
- t.index :verified_at, name: :widget_registry_pending_verification, order: "NULLS FIRST", where: "((state = 2) AND (verification_state = 0))"
- end
- end
- end
- end
-
- def down
- drop_table :widget_registry
- end
- end
- ```
-
-1. Create `ee/app/models/geo/widget_registry.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- class Geo::WidgetRegistry < Geo::BaseRegistry
- include ::Geo::ReplicableRegistry
- include ::Geo::VerifiableRegistry
-
- MODEL_CLASS = ::Widget
- MODEL_FOREIGN_KEY = :widget_id
-
- belongs_to :widget, class_name: 'Widget'
- end
- ```
-
-1. Update `REGISTRY_CLASSES` in `ee/app/workers/geo/secondary/registry_consistency_worker.rb`.
-1. Add `widget_registry` to `ActiveSupport::Inflector.inflections` in `config/initializers_before_autoloader/000_inflections.rb`.
-1. Create `ee/spec/factories/geo/widget_registry.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- FactoryBot.define do
- factory :geo_widget_registry, class: 'Geo::WidgetRegistry' do
- widget
- state { Geo::WidgetRegistry.state_value(:pending) }
-
- trait :synced do
- state { Geo::WidgetRegistry.state_value(:synced) }
- last_synced_at { 5.days.ago }
- end
-
- trait :failed do
- state { Geo::WidgetRegistry.state_value(:failed) }
- last_synced_at { 1.day.ago }
- retry_count { 2 }
- last_sync_failure { 'Random error' }
- end
-
- trait :started do
- state { Geo::WidgetRegistry.state_value(:started) }
- last_synced_at { 1.day.ago }
- retry_count { 0 }
- end
- end
- end
- ```
-
-1. Create `ee/spec/models/geo/widget_registry_spec.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- require 'spec_helper'
-
- RSpec.describe Geo::WidgetRegistry, :geo, type: :model do
- let_it_be(:registry) { create(:geo_widget_registry) }
-
- specify 'factory is valid' do
- expect(registry).to be_valid
- end
-
- include_examples 'a Geo framework registry'
- include_examples 'a Geo verifiable registry'
-
- describe '.find_registry_differences' do
- ... # To be implemented
- end
- end
- ```
-
-Widgets should now be replicated by Geo.
-
-#### Verification
-
-There are two ways to add verification related fields so that the Geo primary
-can track verification state.
-
-##### Option 1: Add verification state fields to the existing `widgets` table itself
-
-1. Add a migration to add columns ordered according to [our guidelines](../ordering_table_columns.md)
- for verification state to the widgets table:
-
- ```ruby
- # frozen_string_literal: true
-
- class AddVerificationStateToWidgets < ActiveRecord::Migration[6.0]
- DOWNTIME = false
-
- def change
- change_table(:widgets) do |t|
- t.integer :verification_state, default: 0, limit: 2, null: false
- t.column :verification_started_at, :datetime_with_timezone
- t.integer :verification_retry_count, limit: 2
- t.column :verification_retry_at, :datetime_with_timezone
- t.column :verified_at, :datetime_with_timezone
- t.binary :verification_checksum, using: 'verification_checksum::bytea'
-
- # rubocop:disable Migration/AddLimitToTextColumns
- t.text :verification_failure
- # rubocop:enable Migration/AddLimitToTextColumns
- end
- end
- end
- ```
-
-1. Adding a `text` column also [requires](../database/strings_and_the_text_data_type.md#add-a-text-column-to-an-existing-table)
- setting a limit:
-
- ```ruby
- # frozen_string_literal: true
-
- class AddVerificationFailureLimitToWidgets < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- disable_ddl_transaction!
-
- CONSTRAINT_NAME = 'widget_verification_failure_text_limit'
-
- def up
- add_text_limit :widget, :verification_failure, 255, constraint_name: CONSTRAINT_NAME
- end
-
- def down
- remove_check_constraint(:widget, CONSTRAINT_NAME)
- end
- end
- ```
-
-1. Add indexes on verification fields to ensure verification can be performed efficiently:
-
- Some or all of these indexes can be omitted if the table is guaranteed to be small. Ask a database expert if you are unsure.
-
- ```ruby
- # frozen_string_literal: true
-
- class AddVerificationIndexesToWidgets < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
- VERIFICATION_STATE_INDEX_NAME = "index_widgets_verification_state"
- PENDING_VERIFICATION_INDEX_NAME = "index_widgets_pending_verification"
- FAILED_VERIFICATION_INDEX_NAME = "index_widgets_failed_verification"
- NEEDS_VERIFICATION_INDEX_NAME = "index_widgets_needs_verification"
-
- disable_ddl_transaction!
-
- def up
- add_concurrent_index :widgets, :verification_state, name: VERIFICATION_STATE_INDEX_NAME
- add_concurrent_index :widgets, :verified_at, where: "(verification_state = 0)", order: { verified_at: 'ASC NULLS FIRST' }, name: PENDING_VERIFICATION_INDEX_NAME
- add_concurrent_index :widgets, :verification_retry_at, where: "(verification_state = 3)", order: { verification_retry_at: 'ASC NULLS FIRST' }, name: FAILED_VERIFICATION_INDEX_NAME
- add_concurrent_index :widgets, :verification_state, where: "(verification_state = 0 OR verification_state = 3)", name: NEEDS_VERIFICATION_INDEX_NAME
- end
-
- def down
- remove_concurrent_index_by_name :widgets, VERIFICATION_STATE_INDEX_NAME
- remove_concurrent_index_by_name :widgets, PENDING_VERIFICATION_INDEX_NAME
- remove_concurrent_index_by_name :widgets, FAILED_VERIFICATION_INDEX_NAME
- remove_concurrent_index_by_name :widgets, NEEDS_VERIFICATION_INDEX_NAME
- end
- end
- ```
-
-1. Add the `Gitlab::Geo::VerificationState` concern to the `widget` model if it is not already included in `Gitlab::Geo::ReplicableModel`:
-
- ```ruby
- class Widget < ApplicationRecord
- ...
- include ::Gitlab::Geo::VerificationState
- ...
- end
- ```
-
-##### Option 2: Create a separate `widget_states` table with verification state fields
-
-1. Create a `widget_states` table and add an index on `verification_state` to ensure verification can be performed efficiently. Order the columns according to [the guidelines](../ordering_table_columns.md):
-
- ```ruby
- # frozen_string_literal: true
-
- class CreateWidgetStates < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- disable_ddl_transaction!
-
- def up
- unless table_exists?(:widget_states)
- with_lock_retries do
- create_table :widget_states, id: false do |t|
- t.references :widget, primary_key: true, null: false, foreign_key: { on_delete: :cascade }
- t.integer :verification_state, default: 0, limit: 2, null: false
- t.column :verification_started_at, :datetime_with_timezone
- t.datetime_with_timezone :verification_retry_at
- t.datetime_with_timezone :verified_at
- t.integer :verification_retry_count, limit: 2
- t.binary :verification_checksum, using: 'verification_checksum::bytea'
- t.text :verification_failure
-
- t.index :verification_state, name: "index_widget_states_on_verification_state"
- end
- end
- end
-
- add_text_limit :widget_states, :verification_failure, 255
- end
-
- def down
- drop_table :widget_states
- end
- end
- ```
-
-1. Add the following lines to the `widget_state.rb` model:
-
- ```ruby
- class WidgetState < ApplicationRecord
- ...
- self.primary_key = :widget_id
-
- include ::Gitlab::Geo::VerificationState
-
- belongs_to :widget, inverse_of: :widget_state
- ...
- end
- ```
-
-1. Add the following lines to the `widget` model:
-
- ```ruby
- class Widget < ApplicationRecord
- ...
- has_one :widget_state, inverse_of: :widget
-
- delegate :verification_retry_at, :verification_retry_at=,
- :verified_at, :verified_at=,
- :verification_checksum, :verification_checksum=,
- :verification_failure, :verification_failure=,
- :verification_retry_count, :verification_retry_count=,
- to: :widget_state
- ...
- end
- ```
-
-To do: Add verification on secondaries. This should be done as part of
-[Geo: Self Service Framework - First Implementation for Package File verification](https://gitlab.com/groups/gitlab-org/-/epics/1817)
-
-Widgets should now be verified by Geo.
-
-#### Metrics
-
-Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in
-`GeoNodeStatus` for display in the UI, and sent to Prometheus:
-
-1. Add fields `widgets_count`, `widgets_checksummed_count`,
- `widgets_checksum_failed_count`, `widgets_synced_count`,
- `widgets_failed_count`, and `widgets_registry_count` to
- `GET /geo_nodes/status` example response in
- `doc/api/geo_nodes.md`.
-1. Add the same fields to `GET /geo_nodes/status` example response in
- `ee/spec/fixtures/api/schemas/public_api/v4/geo_node_status.json`.
-1. Add fields `geo_widgets`, `geo_widgets_checksummed`,
- `geo_widgets_checksum_failed`, `geo_widgets_synced`,
- `geo_widgets_failed`, and `geo_widgets_registry` to
- `Sidekiq metrics` table in
- `doc/administration/monitoring/prometheus/gitlab_metrics.md`.
-1. Add the following to the parameterized table in
- `ee/spec/models/geo_node_status_spec.rb`:
-
- ```ruby
- Geo::WidgetReplicator | :widget | :geo_widget_registry
- ```
-
-1. Add the following to `spec/factories/widgets.rb`:
-
- ```ruby
- trait(:verification_succeeded) do
- with_file
- verification_checksum { 'abc' }
- verification_state { Widget.verification_state_value(:verification_succeeded) }
- end
-
- trait(:verification_failed) do
- with_file
- verification_failure { 'Could not calculate the checksum' }
- verification_state { Widget.verification_state_value(:verification_failed) }
- end
- ```
-
-1. Make sure the factory also allows setting a `project` attribute. If the model
- does not have a direct relation to a project, you can use a `transient`
- attribute. Check out `spec/factories/merge_request_diffs.rb` for an example.
-
-Widget replication and verification metrics should now be available in the API,
-the Admin Area UI, and Prometheus.
-
-#### GraphQL API
-
-1. Add a new field to `GeoNodeType` in
- `ee/app/graphql/types/geo/geo_node_type.rb`:
-
- ```ruby
- field :widget_registries, ::Types::Geo::WidgetRegistryType.connection_type,
- null: true,
- resolver: ::Resolvers::Geo::WidgetRegistriesResolver,
- description: 'Find widget registries on this Geo node',
- feature_flag: :geo_widget_replication
- ```
-
-1. Add the new `widget_registries` field name to the `expected_fields` array in
- `ee/spec/graphql/types/geo/geo_node_type_spec.rb`.
-1. Create `ee/app/graphql/resolvers/geo/widget_registries_resolver.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- module Resolvers
- module Geo
- class WidgetRegistriesResolver < BaseResolver
- include RegistriesResolver
- end
- end
- end
- ```
-
-1. Create `ee/spec/graphql/resolvers/geo/widget_registries_resolver_spec.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- require 'spec_helper'
-
- RSpec.describe Resolvers::Geo::WidgetRegistriesResolver do
- it_behaves_like 'a Geo registries resolver', :geo_widget_registry
- end
- ```
-
-1. Create `ee/app/finders/geo/widget_registry_finder.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- module Geo
- class WidgetRegistryFinder
- include FrameworkRegistryFinder
- end
- end
- ```
-
-1. Create `ee/spec/finders/geo/widget_registry_finder_spec.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- require 'spec_helper'
-
- RSpec.describe Geo::WidgetRegistryFinder do
- it_behaves_like 'a framework registry finder', :geo_widget_registry
- end
- ```
-
-1. Create `ee/app/graphql/types/geo/widget_registry_type.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- module Types
- module Geo
- # rubocop:disable Graphql/AuthorizeTypes because it is included
- class WidgetRegistryType < BaseObject
- include ::Types::Geo::RegistryType
-
- graphql_name 'WidgetRegistry'
- description 'Represents the Geo sync and verification state of a widget'
-
- field :widget_id, GraphQL::ID_TYPE, null: false, description: 'ID of the Widget'
- end
- end
- end
- ```
-
-1. Create `ee/spec/graphql/types/geo/widget_registry_type_spec.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- require 'spec_helper'
-
- RSpec.describe GitlabSchema.types['WidgetRegistry'] do
- it_behaves_like 'a Geo registry type'
-
- it 'has the expected fields (other than those included in RegistryType)' do
- expected_fields = %i[widget_id]
-
- expect(described_class).to have_graphql_fields(*expected_fields).at_least
- end
- end
- ```
-
-1. Add integration tests for providing Widget registry data to the frontend via
- the GraphQL API, by duplicating and modifying the following shared examples
- in `ee/spec/requests/api/graphql/geo/registries_spec.rb`:
-
- ```ruby
- it_behaves_like 'gets registries for', {
- field_name: 'widgetRegistries',
- registry_class_name: 'WidgetRegistry',
- registry_factory: :geo_widget_registry,
- registry_foreign_key_field_name: 'widgetId'
- }
- ```
-
-1. Update the GraphQL reference documentation:
-
- ```shell
- bundle exec rake gitlab:graphql:compile_docs
- ```
-
-Individual widget synchronization and verification data should now be available
-via the GraphQL API.
-
-Make sure to replicate the "update" events. Geo Framework does not currently support
-replicating "update" events because all entities added to the framework, by this time,
-are immutable. If this is the case
-for the entity you're going to add, follow <https://gitlab.com/gitlab-org/gitlab/-/issues/118743>
-and <https://gitlab.com/gitlab-org/gitlab/-/issues/118745> as examples to add the new event type.
-Also, remove this notice when you've added it.
-
-#### Admin UI
-
-To do: This should be done as part of
-[Geo: Implement frontend for Self-Service Framework replicables](https://gitlab.com/groups/gitlab-org/-/epics/2525)
-
-Widget sync and verification data (aggregate and individual) should now be
-available in the Admin UI.
-
-#### Releasing the feature
-
-1. In `ee/config/feature_flags/development/geo_widget_replication.yml`, set `default_enabled: true`
-
-1. In `ee/app/replicators/geo/widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
-
- ```ruby
- module Geo
- class WidgetReplicator < Gitlab::Geo::Replicator
- ...
-
- # REMOVE THIS METHOD
- def self.replication_enabled_by_default?
- false
- end
- # REMOVE THIS METHOD
-
- ...
- end
- end
- ```
-
-1. In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
-
- ```ruby
- field :widget_registries, ::Types::Geo::WidgetRegistryType.connection_type,
- null: true,
- resolver: ::Resolvers::Geo::WidgetRegistriesResolver,
- description: 'Find widget registries on this Geo node',
- feature_flag: :geo_widget_replication # REMOVE THIS LINE
- ```
+To implement Geo replication of a new blob-type Model, [open an issue with the provided issue template](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Geo%3A%20Replicate%20a%20new%20blob%20type).
### Repository Replicator Strategy
-Models that refer to any repository on the disk
-can be easily supported by Geo with the `Geo::RepositoryReplicatorStrategy` module.
-
-For example, to add support for files referenced by a `Gizmos` model with a
-`gizmos` table, you would perform the following steps.
-
-#### Replication
-
-1. Include `Gitlab::Geo::ReplicableModel` in the `Gizmo` class, and specify
- the Replicator class `with_replicator Geo::GizmoReplicator`.
-
- At this point the `Gizmo` class should look like this:
-
- ```ruby
- # frozen_string_literal: true
-
- class Gizmo < ApplicationRecord
- include ::Gitlab::Geo::ReplicableModel
-
- with_replicator Geo::GizmoReplicator
-
- # @param primary_key_in [Range, Gizmo] arg to pass to primary_key_in scope
- # @return [ActiveRecord::Relation<Gizmo>] everything that should be synced to this node, restricted by primary key
- def self.replicables_for_current_secondary(primary_key_in)
- # Should be implemented. The idea of the method is to restrict
- # the set of synced items depending on synchronization settings
- end
-
- # Geo checks this method in FrameworkRepositorySyncService to avoid
- # snapshotting repositories using object pools
- def pool_repository
- nil
- end
- ...
- end
- ```
-
- Pay some attention to method `pool_repository`. Not every repository type uses
- repository pooling. As Geo prefers to use repository snapshotting, it can lead to data loss.
- Make sure to overwrite `pool_repository` so it returns nil for repositories that do not
- have pools.
-
- If there is a common constraint for records to be available for replication,
- make sure to also overwrite the `available_replicables` scope.
-
-1. Create `ee/app/replicators/geo/gizmo_replicator.rb`. Implement the
- `#repository` method which should return a `<Repository>` instance,
- and implement the class method `.model` to return the `Gizmo` class:
-
- ```ruby
- # frozen_string_literal: true
-
- module Geo
- class GizmoReplicator < Gitlab::Geo::Replicator
- include ::Geo::RepositoryReplicatorStrategy
-
- def self.model
- ::Gizmo
- end
-
- def repository
- model_record.repository
- end
-
- def self.git_access_class
- ::Gitlab::GitAccessGizmo
- end
-
- # The feature flag follows the format `geo_#{replicable_name}_replication`,
- # so here it would be `geo_gizmo_replication`
- def self.replication_enabled_by_default?
- false
- end
- end
- end
- ```
-
-1. Generate the feature flag definition file by running the feature flag command
- and running through the steps:
-
- ```shell
- bin/feature-flag --ee geo_gizmo_replication --type development --group 'group::geo'
- ```
-
-1. Make sure Geo push events are created. Usually it needs some
- change in the `app/workers/post_receive.rb` file. Example:
-
- ```ruby
- def replicate_gizmo_changes(gizmo)
- if ::Gitlab::Geo.primary?
- gizmo.replicator.handle_after_update if gizmo
- end
- end
- ```
-
- See `app/workers/post_receive.rb` for more examples.
-
-1. Make sure the repository removal is also handled. You may need to add something
- like the following in the destroy service of the repository:
-
- ```ruby
- gizmo.replicator.handle_after_destroy if gizmo.repository
- ```
-
-1. Add this replicator class to the method `replicator_classes` in
- `ee/lib/gitlab/geo.rb`:
-
- ```ruby
- REPLICATOR_CLASSES = [
- ...
- ::Geo::PackageFileReplicator,
- ::Geo::GizmoReplicator
- ]
- end
- ```
-
-1. Create `ee/spec/replicators/geo/gizmo_replicator_spec.rb` and perform
- the necessary setup to define the `model_record` variable for the shared
- examples:
-
- ```ruby
- # frozen_string_literal: true
-
- require 'spec_helper'
-
- RSpec.describe Geo::GizmoReplicator do
- let(:model_record) { build(:gizmo) }
-
- include_examples 'a repository replicator'
- end
- ```
-
-1. Create the `gizmo_registry` table, with columns ordered according to [our guidelines](../ordering_table_columns.md) so Geo secondaries can track the sync and
- verification state of each Gizmo. This migration belongs in `ee/db/geo/migrate`:
-
- ```ruby
- # frozen_string_literal: true
-
- class CreateGizmoRegistry < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- disable_ddl_transaction!
-
- def up
- create_table :gizmo_registry, id: :bigserial, force: :cascade do |t|
- t.datetime_with_timezone :retry_at
- t.datetime_with_timezone :last_synced_at
- t.datetime_with_timezone :created_at, null: false
- t.bigint :gizmo_id, null: false
- t.integer :state, default: 0, null: false, limit: 2
- t.integer :retry_count, default: 0, limit: 2
- t.string :last_sync_failure, limit: 255
- t.boolean :force_to_redownload
- t.boolean :missing_on_primary
-
- t.index :gizmo_id, name: :index_gizmo_registry_on_gizmo_id, unique: true
- t.index :retry_at
- t.index :state
- end
- end
-
- def down
- drop_table :gizmo_registry
- end
- end
- ```
-
-1. Create `ee/app/models/geo/gizmo_registry.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- class Geo::GizmoRegistry < Geo::BaseRegistry
- include Geo::ReplicableRegistry
-
- MODEL_CLASS = ::Gizmo
- MODEL_FOREIGN_KEY = :gizmo_id
-
- belongs_to :gizmo, class_name: 'Gizmo'
- end
- ```
-
-1. Update `REGISTRY_CLASSES` in `ee/app/workers/geo/secondary/registry_consistency_worker.rb`.
-1. Add `gizmo_registry` to `ActiveSupport::Inflector.inflections` in `config/initializers_before_autoloader/000_inflections.rb`.
-1. Create `ee/spec/factories/geo/gizmo_registry.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- FactoryBot.define do
- factory :geo_gizmo_registry, class: 'Geo::GizmoRegistry' do
- gizmo
- state { Geo::GizmoRegistry.state_value(:pending) }
-
- trait :synced do
- state { Geo::GizmoRegistry.state_value(:synced) }
- last_synced_at { 5.days.ago }
- end
-
- trait :failed do
- state { Geo::GizmoRegistry.state_value(:failed) }
- last_synced_at { 1.day.ago }
- retry_count { 2 }
- last_sync_failure { 'Random error' }
- end
-
- trait :started do
- state { Geo::GizmoRegistry.state_value(:started) }
- last_synced_at { 1.day.ago }
- retry_count { 0 }
- end
- end
- end
- ```
-
-1. Create `ee/spec/models/geo/gizmo_registry_spec.rb`:
-
- ```ruby
- # frozen_string_literal: true
-
- require 'spec_helper'
-
- RSpec.describe Geo::GizmoRegistry, :geo, type: :model do
- let_it_be(:registry) { create(:geo_gizmo_registry) }
-
- specify 'factory is valid' do
- expect(registry).to be_valid
- end
-
- include_examples 'a Geo framework registry'
- end
- ```
-
-1. Make sure the newly added repository type can be accessed by a secondary.
- You may need to make some changes to one of the Git access classes.
-
- Gizmos should now be replicated by Geo.
-
-#### Metrics
-
-You need to make the same changes as for Blob Replicator Strategy.
-You need to make the same changes for the [metrics as in the Blob Replicator Strategy](#metrics).
-
-#### GraphQL API
-
-You need to make the same changes for the GraphQL API [as in the Blob Replicator Strategy](#graphql-api).
+Models that refer to any Git repository on disk are supported by Geo with the `Geo::RepositoryReplicatorStrategy` module. For example, see how [Geo replication was implemented for Group-level Wikis](https://gitlab.com/gitlab-org/gitlab/-/issues/208147). Note that this issue does not implement verification, since verification of Git repositories was not yet added to the Geo self-service framework. An example implementing verification can be found in the merge request to [Add Snippet repository verification](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56596).
-#### Releasing the feature
+Each Git repository is expected to have its own primary ID and model.
-You need to make the same changes for [releasing the feature as in the Blob Replicator Strategy](#releasing-the-feature).
+To implement Geo replication of a new Git repository-type Model, [open an issue with the provided issue template](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Geo%3A%20Replicate%20a%20new%20Git%20repository%20type).