1# frozen_string_literal: true 2 3module RepositoryStorageMovable 4 extend ActiveSupport::Concern 5 include AfterCommitQueue 6 7 included do 8 scope :order_created_at_desc, -> { order(created_at: :desc) } 9 10 validates :container, presence: true 11 validates :state, presence: true 12 validates :source_storage_name, 13 on: :create, 14 presence: true, 15 inclusion: { in: ->(_) { Gitlab.config.repositories.storages.keys } } 16 validates :destination_storage_name, 17 on: :create, 18 presence: true, 19 inclusion: { in: ->(_) { Gitlab.config.repositories.storages.keys } } 20 validate :container_repository_writable, on: :create 21 22 default_value_for(:destination_storage_name, allows_nil: false) do 23 Repository.pick_storage_shard 24 end 25 26 state_machine initial: :initial do 27 event :schedule do 28 transition initial: :scheduled 29 end 30 31 event :start do 32 transition scheduled: :started 33 end 34 35 event :finish_replication do 36 transition started: :replicated 37 end 38 39 event :finish_cleanup do 40 transition replicated: :finished 41 end 42 43 event :do_fail do 44 transition [:initial, :scheduled, :started] => :failed 45 transition replicated: :cleanup_failed 46 end 47 48 around_transition initial: :scheduled do |storage_move, block| 49 block.call 50 51 begin 52 storage_move.container.set_repository_read_only!(skip_git_transfer_check: true) 53 rescue StandardError => err 54 storage_move.add_error(err.message) 55 next false 56 end 57 58 storage_move.run_after_commit do 59 storage_move.schedule_repository_storage_update_worker 60 end 61 62 true 63 end 64 65 before_transition started: :replicated do |storage_move| 66 storage_move.container.set_repository_writable! 67 68 storage_move.update_repository_storage(storage_move.destination_storage_name) 69 end 70 71 after_transition started: :replicated do |storage_move| 72 # We have several scripts in place that replicate some statistics information 73 # to other databases. Some of them depend on the updated_at column 74 # to identify the models they need to extract. 75 # 76 # If we don't update the `updated_at` of the container after a repository storage move, 77 # the scripts won't know that they need to sync them. 78 # 79 # See https://gitlab.com/gitlab-data/analytics/-/issues/7868 80 storage_move.container.touch 81 end 82 83 before_transition started: :failed do |storage_move| 84 storage_move.container.set_repository_writable! 85 end 86 87 state :initial, value: 1 88 state :scheduled, value: 2 89 state :started, value: 3 90 state :finished, value: 4 91 state :failed, value: 5 92 state :replicated, value: 6 93 state :cleanup_failed, value: 7 94 end 95 end 96 97 # Projects, snippets, and group wikis has different db structure. In projects, 98 # we need to update some columns in this step, but we don't with the other resources. 99 # 100 # Therefore, we create this No-op method for snippets and wikis and let project 101 # overwrite it in their implementation. 102 def update_repository_storage(new_storage) 103 # No-op 104 end 105 106 def schedule_repository_storage_update_worker 107 raise NotImplementedError 108 end 109 110 def add_error(message) 111 errors.add(error_key, message) 112 end 113 114 private 115 116 def container_repository_writable 117 add_error(_('is read-only')) if container&.repository_read_only? 118 end 119 120 def error_key 121 raise NotImplementedError 122 end 123end 124