Commit 7b0131f0 authored by Valery Sizov's avatar Valery Sizov

Expose an API for Design Repositories for Geo Admin area

Created API endpoints
parent cba8f642
...@@ -7,17 +7,17 @@ module Geo ...@@ -7,17 +7,17 @@ module Geo
end end
def count_synced def count_synced
registries_for_design_repositories registries
.merge(Geo::DesignRegistry.synced).count .merge(Geo::DesignRegistry.synced).count
end end
def count_failed def count_failed
registries_for_design_repositories registries
.merge(Geo::DesignRegistry.failed).count .merge(Geo::DesignRegistry.failed).count
end end
def count_registry def count_registry
registries_for_design_repositories.count registries.count
end end
private private
...@@ -26,7 +26,7 @@ module Geo ...@@ -26,7 +26,7 @@ module Geo
current_node.projects.inner_join_design_management current_node.projects.inner_join_design_management
end end
def registries_for_design_repositories def registries
designs_repositories designs_repositories
.inner_join_design_registry .inner_join_design_registry
end end
......
...@@ -35,6 +35,21 @@ class Geo::DesignRegistry < Geo::BaseRegistry ...@@ -35,6 +35,21 @@ class Geo::DesignRegistry < Geo::BaseRegistry
end end
end end
# Search for a list of projects associated with registries,
# based on the query given in `query`.
#
# @param [String] query term that will search over :path, :name and :description
def self.with_search_by_project(query)
where(project: Geo::Fdw::Project.search(query))
end
def self.search(params)
designs_repositories = self
designs_repositories = designs_repositories.with_state(params[:sync_status]) if params[:sync_status].present?
designs_repositories = designs_repositories.with_search_by_project(params[:search]) if params[:search].present?
designs_repositories
end
def fail_sync!(message, error, attrs = {}) def fail_sync!(message, error, attrs = {})
new_retry_count = retry_count + 1 new_retry_count = retry_count + 1
......
# frozen_string_literal: true
class GeoDesignRegistryEntity < Grape::Entity
expose :project_id
expose :name do |design|
design.project.path_with_namespace
end
expose :state
expose :last_synced_at
end
# frozen_string_literal: true
class GeoDesignRegistrySerializer < BaseSerializer
entity GeoDesignRegistryEntity
end
# frozen_string_literal: true
module API
class GeoReplication < Grape::API
include PaginationParams
include APIGuard
include ::Gitlab::Utils::StrongMemoize
before do
authenticated_as_admin!
not_found!('Geo node not found') unless Gitlab::Geo.current_node
forbidden!('Designs can only be requested from a secondary node') unless Gitlab::Geo.current_node.secondary?
end
resource :geo_replication do
resource :designs do
# Get designs for the current Geo node
#
# Example request:
# GET /geo_replication/designs
desc 'Get designs for the current Geo node' do
success ::GeoDesignRegistryEntity
end
params do
optional :search, type: String, desc: 'Query term that will search over :path, :name and :description'
optional :sync_status, type: String, values: %w[failed synced pending], desc: 'The state of sync'
use :pagination
end
get do
design_registries = ::Geo::DesignRegistry.search(params)
present paginate(design_registries), with: ::GeoDesignRegistryEntity
end
# Resync design for the current Geo node
#
# Example request:
# PUT /geo_replication/designs/:id/resync
desc 'Resync design for the current Geo node' do
success ::GeoDesignRegistryEntity
end
params do
optional :id, type: Integer, desc: 'ID of project'
end
put ':id/resync' do
::Geo::DesignRegistry.find_by!(project_id: params[:id]).repository_updated! # rubocop: disable CodeReuse/ActiveRecord
:ok
end
# Resync all the designs for the current Geo node
#
# Example request:
# POST /geo_replication/designs/resync
desc 'Resync all the design for the current Geo node' do
success ::GeoDesignRegistryEntity
end
post 'resync' do
::Geo::DesignRegistry.update_all(state: :pending)
:ok
end
end
end
end
end
...@@ -22,6 +22,7 @@ module EE ...@@ -22,6 +22,7 @@ module EE
mount ::API::FeatureFlagScopes mount ::API::FeatureFlagScopes
mount ::API::ContainerRegistryEvent mount ::API::ContainerRegistryEvent
mount ::API::Geo mount ::API::Geo
mount ::API::GeoReplication
mount ::API::GeoNodes mount ::API::GeoNodes
mount ::API::IssueLinks mount ::API::IssueLinks
mount ::API::Ldap mount ::API::Ldap
......
...@@ -20,7 +20,7 @@ module EE ...@@ -20,7 +20,7 @@ module EE
override :whitelisted_routes override :whitelisted_routes
def whitelisted_routes def whitelisted_routes
super || geo_node_update_route? || geo_proxy_git_push_ssh_route? super || geo_node_update_route? || geo_proxy_git_push_ssh_route? || geo_api_route?
end end
def geo_node_update_route? def geo_node_update_route?
...@@ -45,6 +45,12 @@ module EE ...@@ -45,6 +45,12 @@ module EE
routes.flatten.include?(request.path) routes.flatten.include?(request.path)
end end
def geo_api_route?
::Gitlab::Middleware::ReadOnly::API_VERSIONS.any? do |version|
request.path.include?("/api/v#{version}/geo_replication")
end
end
end end
end end
end end
......
{
"type": "object",
"required" : [
"project_id",
"name",
"state",
"last_synced_at"
],
"properties" : {
"project_id": { "type": "integer" },
"name": { "type": ["string"] },
"state": { "type": ["string"] },
"last_synced_at": { "type": ["string", "null"] }
},
"additionalProperties": false
}
{
"type": "array",
"items": { "$ref": "geo_design.json" }
}
...@@ -69,5 +69,12 @@ describe Gitlab::Middleware::ReadOnly do ...@@ -69,5 +69,12 @@ describe Gitlab::Middleware::ReadOnly do
it_behaves_like 'whitelisted request', :delete, '/admin/geo/uploads/1' it_behaves_like 'whitelisted request', :delete, '/admin/geo/uploads/1'
end end
it 'expects geo replication node api requests to be allowed' do
response = request.post("/api/#{API::API.version}/geo_replication/designs/resync")
expect(response).not_to be_redirect
expect(subject).not_to disallow_request
end
end end
end end
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe Geo::DesignRegistry, :geo do describe Geo::DesignRegistry, :geo do
set(:design_registry) { create(:geo_design_registry) } let!(:design_registry) { create(:geo_design_registry) }
describe 'relationships' do describe 'relationships' do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
...@@ -13,6 +13,35 @@ describe Geo::DesignRegistry, :geo do ...@@ -13,6 +13,35 @@ describe Geo::DesignRegistry, :geo do
let(:registry) { create(:geo_design_registry) } let(:registry) { create(:geo_design_registry) }
end end
describe '#search', :geo_fdw do
let!(:failed_registry) { create(:geo_design_registry, :sync_failed) }
let!(:synced_registry) { create(:geo_design_registry, :synced) }
it 'all the registries' do
result = described_class.search({})
expect(result.count).to eq(3)
end
it 'finds by state' do
result = described_class.search({ sync_status: :failed })
expect(result.count).to eq(1)
expect(result.first.state).to eq('failed')
end
it 'finds by name' do
project = create(:project, name: 'bla')
create(:design, project: project)
create(:geo_design_registry, project: project)
result = described_class.search({ search: 'bla' })
expect(result.count).to eq(1)
expect(result.first.project_id).to eq(project.id)
end
end
describe '#finish_sync!' do describe '#finish_sync!' do
let(:design_registry) { create(:geo_design_registry, :sync_started) } let(:design_registry) { create(:geo_design_registry, :sync_started) }
......
# frozen_string_literal: true
require 'spec_helper'
describe API::GeoReplication, :geo, :geo_fdw, api: true do
include ApiHelpers
include ::EE::GeoHelpers
include_context 'custom session'
let(:primary) { create(:geo_node, :primary) }
let(:secondary) { create(:geo_node) }
let(:secondary_status) { create(:geo_node_status, :healthy, geo_node: secondary) }
let(:admin) { create(:admin) }
let(:user) { create(:user) }
before do
set_current_geo_node!(secondary)
end
describe 'GET /geo_replication/designs' do
it 'retrieves the designs if admin is logged in' do
get api("/geo_replication/designs", admin)
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/geo_designs', dir: 'ee')
end
it 'retrieves the designs according to search term' do
project = create(:project, name: 'bla')
create(:design, project: project)
create(:geo_design_registry, project: project)
project1 = create(:project, name: 'not-what-we-search-for')
create(:design, project: project1)
create(:geo_design_registry, project: project1)
get api("/geo_replication/designs", admin), params: { search: 'bla' }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/geo_designs', dir: 'ee')
expect(json_response.size).to eq(1)
expect(json_response.first['project_id']).to eq(project.id)
end
it 'denies access if not admin' do
get api('/geo_replication/designs', user)
expect(response).to have_gitlab_http_status(403)
end
end
describe 'PUT /geo_replication/designs/:id/resync' do
it 'marks registry record for resync' do
project = create(:project)
create(:design, project: project)
design_registry = create(:geo_design_registry, :synced, project: project)
put api("/geo_replication/designs/#{project.id}/resync", admin)
expect(response).to have_gitlab_http_status(200)
expect(design_registry.reload.state).to eq('pending')
end
it 'denies access if not admin' do
put api('/geo_replication/designs/1/resync', user)
expect(response).to have_gitlab_http_status(403)
end
end
describe 'POST /geo_replication/designs/resync' do
it 'marks registry record for resync' do
create(:geo_design_registry, :synced)
create(:geo_design_registry, :synced)
post api("/geo_replication/designs/resync", admin)
expect(response).to have_gitlab_http_status(201)
::Geo::DesignRegistry.all.each do |registry|
expect(registry.state).to eq('pending')
end
end
it 'denies access if not admin' do
post api('/geo_replication/designs/resync', user)
expect(response).to have_gitlab_http_status(403)
end
end
end
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