repository_backfill_service.rb 3.16 KB
Newer Older
1 2
module Geo
  class RepositoryBackfillService
3
    attr_reader :project_id
4

5 6
    LEASE_TIMEOUT    = 8.hours.freeze
    LEASE_KEY_PREFIX = 'repository_backfill_service'.freeze
7

8
    def initialize(project_id)
9
      @project_id = project_id
10 11 12
    end

    def execute
13
      try_obtain_lease do
14
        log('Started repository sync')
15 16
        started_at, finished_at = fetch_repositories
        update_registry(started_at, finished_at)
17
        log('Finished repository sync')
18
      end
19 20
    rescue ActiveRecord::RecordNotFound
      logger.error("Couldn't find project with ID=#{project_id}, skipping syncing")
21 22 23 24
    end

    private

25 26 27 28
    def project
      @project ||= Project.find(project_id)
    end

29
    def fetch_repositories
30 31 32 33
      started_at  = DateTime.now
      finished_at = nil

      begin
34 35 36
        fetch_project_repository
        fetch_wiki_repository
        expire_repository_caches
37

38 39
        finished_at = DateTime.now
      rescue Gitlab::Shell::Error => e
40
        Rails.logger.error "Error syncing repository for project #{project.path_with_namespace}: #{e}"
41
      end
42

43
      [started_at, finished_at]
44
    end
45

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
    def fetch_project_repository
      log('Fetching project repository')
      project.create_repository unless project.repository_exists?
      project.repository.fetch_geo_mirror(ssh_url_to_repo)
    end

    def fetch_wiki_repository
      # Second .wiki call returns a Gollum::Wiki, and it will always create the physical repository when not found
      if project.wiki.wiki.exist?
        log('Fetching wiki repository')
        project.wiki.repository.fetch_geo_mirror(ssh_url_to_wiki)
      end
    end

    def expire_repository_caches
      log('Expiring caches')
      project.repository.after_sync
    end

65
    def try_obtain_lease
66 67
      log('Trying to obtain lease to sync repository')

68
      repository_lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT).try_obtain
69

70 71 72 73
      if repository_lease.nil?
        log('Could not obtain lease to sync repository')
        return
      end
74 75 76

      yield

77 78 79
      # We should release the lease for a repository, only if we have obtained
      # it. If something went wrong when syncing the repository, we should wait
      # for the lease timeout to try again.
80 81
      log('Releasing leases to sync repository')
      Gitlab::ExclusiveLease.cancel(lease_key, repository_lease)
82 83
    end

84 85
    def update_registry(started_at, finished_at)
      log('Updating registry information')
86
      registry = Geo::ProjectRegistry.find_or_initialize_by(project_id: project.id)
87 88 89 90 91
      registry.last_repository_synced_at = started_at
      registry.last_repository_successful_sync_at = finished_at if finished_at
      registry.save
    end

92
    def lease_key
93
      @lease_key ||= "#{LEASE_KEY_PREFIX}:#{project.id}"
94 95
    end

96 97 98 99
    def primary_ssh_path_prefix
      Gitlab::Geo.primary_ssh_path_prefix
    end

100
    def ssh_url_to_repo
101 102 103 104 105
      "#{primary_ssh_path_prefix}#{project.path_with_namespace}.git"
    end

    def ssh_url_to_wiki
      "#{primary_ssh_path_prefix}#{project.path_with_namespace}.wiki.git"
106
    end
107 108 109 110

    def log(message)
      Rails.logger.info "#{self.class.name}: #{message} for project #{project.path_with_namespace} (#{project.id})"
    end
111 112
  end
end