Commit 3573f63a authored by Toon Claes's avatar Toon Claes

Check for readonly? instead on secondary?

To bring the idea of a readonly database to CE, we need to check for
`readonly?` instead of `secondary?`.

See gitlab-org/gitlab-ce#37534.
parent dda69dbc
......@@ -3,18 +3,23 @@
# Automatically sets the layout and ensures an administrator is logged in
class Admin::ApplicationController < ApplicationController
before_action :authenticate_admin!
before_action :display_geo_information
before_action :display_readonly_information
layout 'admin'
def authenticate_admin!
render_404 unless current_user.admin?
end
def display_geo_information
return unless Gitlab::Geo.secondary?
return unless Gitlab::Geo.primary_node_configured?
def display_readonly_information
return unless Gitlab::Database.readonly?
primary_node = view_context.link_to('primary node', Gitlab::Geo.primary_node.url)
flash.now[:notice] = "You are on a secondary (read-only) Geo node. If you want to make any changes, you must visit the #{primary_node}.".html_safe
flash.now[:notice] = readonly_message
end
private
# Overridden in EE
def readonly_message
_('You are on a read-only GitLab instance.').html_safe
end
end
......@@ -12,7 +12,7 @@ module Boards
def index
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
issues = issues.page(params[:page]).per(params[:per] || 20)
make_sure_position_is_set(issues) unless Gitlab::Geo.secondary?
make_sure_position_is_set(issues) unless Gitlab::Database.readonly?
issues = issues.preload(:project,
:milestone,
:assignees,
......
......@@ -4,6 +4,8 @@ class Projects::LfsApiController < Projects::GitHttpClientController
include GitlabRoutingHelper
include LfsRequest
prepend ::EE::Projects::LfsApiController
skip_before_action :lfs_check_access!, only: [:deprecated]
before_action :lfs_check_batch_operation!, only: [:batch]
......@@ -96,14 +98,19 @@ class Projects::LfsApiController < Projects::GitHttpClientController
end
def lfs_check_batch_operation!
if upload_request? && Gitlab::Geo.secondary?
if upload_request? && Gitlab::Database.readonly?
render(
json: {
message: "You cannot write to a secondary GitLab Geo instance. Please use #{geo_primary_default_url_to_repo(project)} instead."
message: lfs_readonly_message
},
content_type: "application/vnd.git-lfs+json",
content_type: 'application/vnd.git-lfs+json',
status: 403
)
end
end
# Overridden in EE
def lfs_readonly_message
_('You cannot write to this read-only GitLab instance.')
end
end
......@@ -115,7 +115,7 @@ class SessionsController < Devise::SessionsController
end
def gitlab_geo_login
return unless Gitlab::Geo.secondary?
return unless Gitlab::Geo.secondary? # TODO?
return if signed_in?
oauth = Gitlab::Geo::OauthSession.new
......@@ -128,7 +128,7 @@ class SessionsController < Devise::SessionsController
end
def gitlab_geo_logout
return unless Gitlab::Geo.secondary?
return unless Gitlab::Geo.secondary? # TODO?
oauth = Gitlab::Geo::OauthSession.new(access_token: session[:access_token])
@geo_logout_state = oauth.generate_logout_state
......
......@@ -152,7 +152,7 @@ module Routable
end
def update_route
return if Gitlab::Geo.secondary?
return if Gitlab::Database.readonly?
prepare_route
route.save
......
......@@ -498,7 +498,7 @@ class MergeRequest < ActiveRecord::Base
end
def check_if_can_be_merged
return unless unchecked? && !Gitlab::Geo.secondary?
return unless unchecked? && !Gitlab::Database.readonly?
can_be_merged =
!broken? && project.repository.can_be_merged?(diff_head_sha, target_branch)
......
......@@ -811,7 +811,7 @@ class Project < ActiveRecord::Base
end
def cache_has_external_issue_tracker
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) unless Gitlab::Database.readonly?
end
def has_wiki?
......@@ -831,7 +831,7 @@ class Project < ActiveRecord::Base
end
def cache_has_external_wiki
update_column(:has_external_wiki, services.external_wikis.any?)
update_column(:has_external_wiki, services.external_wikis.any?) unless Gitlab::Database.readonly?
end
def find_or_initialize_services(exceptions: [])
......
......@@ -477,6 +477,18 @@ class User < ActiveRecord::Base
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end
def remember_me!
raise NotImplementedError unless defined?(super)
super unless ::Gitlab::Database.readonly?
end
def forget_me!
raise NotImplementedError unless defined?(super)
super unless ::Gitlab::Database.readonly?
end
def disable_two_factor!
transaction do
update_attributes(
......
......@@ -14,7 +14,7 @@ module Users
private
def record_activity
Gitlab::UserActivities.record(@author.id) unless Gitlab::Geo.secondary?
Gitlab::UserActivities.record(@author.id) unless Gitlab::Database.readonly?
Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username})")
end
......
......@@ -84,7 +84,7 @@
%p
.js-health
- unless Gitlab::Geo.secondary?
- unless Gitlab::Database.readonly?
.node-actions
- if Gitlab::Geo.license_allows?
- if node.missing_oauth_application?
......
---
title: Create idea of read-only database and add method to check for it
merge_request: 2954
author:
type: changed
......@@ -174,8 +174,8 @@ module Gitlab
ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
ENV['GIT_TERMINAL_PROMPT'] = '0'
# Gitlab Geo Middleware support
config.middleware.insert_after ActionDispatch::Flash, 'Gitlab::Middleware::ReadonlyGeo'
# Gitlab Readonly middleware support
config.middleware.insert_after ActionDispatch::Flash, 'Gitlab::Middleware::Readonly'
config.generators do |g|
g.factory_girl false
......
......@@ -84,13 +84,13 @@ All **Secondary** nodes are read-only.
We have a Rails Middleware that filters any potentially writing operations
and prevent user from trying to update the database and getting a 500 error
(see `Gitlab::Middleware::ReadonlyGeo`).
(see `Gitlab::Middleware::Readonly`).
Database will already be read-only in a replicated setup, so we don't need to
take any extra step for that.
We do use our feature toggle `.secondary?` to coordinate Git operations and do
the correct authorization (denying writing on any secondary node).
We do use our feature toggle `Gitlab::Database.readonly?` to deny write operations
on a node that is in read-only mode. This toggle is also available in CE.
## File Transfers
......
module EE
module Admin
module ApplicationController
def readonly_message
raise NotImplementedError unless defined?(super)
return super unless Gitlab::Geo.secondary_with_primary?
link_to_primary_node = view_context.link_to('primary node', Gitlab::Geo.primary_node.url)
(_('You are on a read-only GitLab instance. If you want to make any changes, you must visit the %{link_to_primary_node}.') % { link_to_primary_node: link_to_primary_node }).html_safe
end
end
end
end
module EE
module Projects
module LfsApiController
def lfs_readonly_message
raise NotImplementedError unless defined?(super)
return super unless ::Gitlab::Geo.secondary_with_primary?
(_('You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead.') % { link_to_primary_node: geo_primary_default_url_to_repo(project) }).html_safe
end
end
end
end
......@@ -273,14 +273,6 @@ module EE
.order(order % quoted_values) # `order` cannot escape for us!
end
def cache_has_external_issue_tracker
super unless ::Gitlab::Geo.secondary?
end
def cache_has_external_wiki
super unless ::Gitlab::Geo.secondary?
end
def execute_hooks(data, hooks_scope = :push_hooks)
super
......
......@@ -84,16 +84,6 @@ module EE
super || auditor?
end
def remember_me!
return if ::Gitlab::Geo.secondary?
super
end
def forget_me!
return if ::Gitlab::Geo.secondary?
super
end
def email_opted_in_source
email_opted_in_source_id == EMAIL_OPT_IN_SOURCE_ID_GITLAB_COM ? 'GitLab.com' : ''
end
......
module EE
module Gitlab
module Database
def self.readonly?
raise NotImplementedError unless defined?(super)
Gitlab::Geo.secondary? || super
end
end
end
end
......@@ -163,9 +163,9 @@ module Banzai
Rails.cache.__send__(:expanded_key, full_cache_key(cache_key, pipeline_name)) # rubocop:disable GitlabSecurity/PublicSend
end
# GitLab EE needs to disable updates on GET requests in Geo
# GitLab needs to disable updates on GET requests if database is readonly
def self.update_object?(object)
!Gitlab::Geo.secondary?
!Gitlab::Database.readonly?
end
end
end
module Gitlab
module Database
extend ::EE::Gitlab::Database
# The max value of INTEGER type is the same between MySQL and PostgreSQL:
# https://www.postgresql.org/docs/9.2/static/datatype-numeric.html
# http://dev.mysql.com/doc/refman/5.7/en/integer-types.html
......@@ -29,6 +31,10 @@ module Gitlab
adapter_name.casecmp('postgresql').zero?
end
def self.readonly?
false
end
def self.version
database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end
......
......@@ -54,6 +54,10 @@ module Gitlab
Gitlab::Geo.primary_node.present?
end
def self.secondary_with_primary?
self.secondary? && self.primary_node_configured?
end
def self.license_allows?
::License.feature_available?(:geo)
end
......
......@@ -20,7 +20,7 @@ module Gitlab
upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.',
receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.',
readonly: 'The repository is temporarily read-only. Please try again later.',
cannot_push_to_secondary_geo: "You can't push code to a secondary GitLab Geo node."
cannot_push_to_readonly: "You can't push code to a read-only GitLab instance."
}.freeze
DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze
......@@ -176,8 +176,8 @@ module Gitlab
raise UnauthorizedError, ERROR_MESSAGES[:readonly]
end
if Gitlab::Geo.secondary?
raise UnauthorizedError, ERROR_MESSAGES[:cannot_push_to_secondary_geo]
if Gitlab::Database.readonly?
raise UnauthorizedError, ERROR_MESSAGES[:cannot_push_to_readonly]
end
if deploy_key
......
module Gitlab
class GitAccessWiki < GitAccess
ERROR_MESSAGES = {
geo: "You can't push code to a secondary GitLab Geo node.",
readonly: "You can't push code to this read-only GitLab instance.",
write_to_wiki: "You are not allowed to write to this project's wiki."
}.freeze
......@@ -18,8 +18,8 @@ module Gitlab
raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki]
end
if Gitlab::Geo.enabled? && Gitlab::Geo.secondary?
raise UnauthorizedError, ERROR_MESSAGES[:geo]
if Gitlab::Geo.enabled? && Gitlab::Database.readonly?
raise UnauthorizedError, ERROR_MESSAGES[:readonly]
end
true
......
module Gitlab
module Middleware
class ReadonlyGeo
class Readonly
DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze
APPLICATION_JSON = 'application/json'.freeze
API_VERSIONS = (3..4)
......@@ -13,9 +13,9 @@ module Gitlab
def call(env)
@env = env
if disallowed_request? && Gitlab::Geo.secondary?
Rails.logger.debug('GitLab Geo: preventing possible non readonly operation')
error_message = 'You cannot do writing operations on a secondary GitLab Geo instance'
if disallowed_request? && Gitlab::Database.readonly?
Rails.logger.debug('GitLab Readonly: preventing possible non readonly operation')
error_message = 'You cannot do writing operations on a read-only GitLab instance'
if json_request?
return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]]
......
require Rails.root.join('ee/lib/ee/gitlab/database')
require Rails.root.join('lib/gitlab/database')
require Rails.root.join('lib/gitlab/database/migration_helpers')
require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes')
......
......@@ -83,8 +83,8 @@ describe EE::User do
expect(subject.reload.remember_created_at).to be_nil
end
it 'does not clear remember_created_at when in a Geo secondary node' do
allow(Gitlab::Geo).to receive(:secondary?) { true }
it 'does not clear remember_created_at when in a GitLab readonly instance' do
allow(Gitlab::Database).to receive(:readonly?) { true }
expect { subject.forget_me! }.not_to change(subject, :remember_created_at)
end
......@@ -99,8 +99,8 @@ describe EE::User do
expect(subject.reload.remember_created_at).not_to be_nil
end
it 'does not update remember_created_at when in a Geo secondary node' do
allow(Gitlab::Geo).to receive(:secondary?) { true }
it 'does not update remember_created_at when in a Geo readonly instance' do
allow(Gitlab::Database).to receive(:readonly?) { true }
expect { subject.remember_me! }.not_to change(subject, :remember_created_at)
end
......
......@@ -36,8 +36,8 @@ describe Banzai::Renderer do
is_expected.to eq('field_html')
end
it "skips database caching on a Geo secondary" do
allow(Gitlab::Geo).to receive(:secondary?).and_return(true)
it "skips database caching on a GitLab readonly instance" do
allow(Gitlab::Database).to receive(:readonly?).and_return(true)
expect(object).to receive(:refresh_markdown_cache!).with(do_update: false)
is_expected.to eq('field_html')
......
......@@ -744,11 +744,10 @@ describe Gitlab::GitAccess do
run_permission_checks(admin: matrix)
end
context "when in a secondary gitlab geo node" do
context "when in a read-only GitLab instance" do
before do
create(:protected_branch, name: 'feature', project: project)
allow(Gitlab::Geo).to receive(:enabled?) { true }
allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Database).to receive(:readonly?) { true }
end
# Only check admin; if an admin can't do it, other roles can't either
......
......@@ -25,15 +25,13 @@ describe Gitlab::GitAccessWiki do
it { expect { subject }.not_to raise_error }
context 'when in a secondary gitlab geo node' do
context 'when in a read-only GitLab instance' do
before do
allow(Gitlab::Geo).to receive(:enabled?) { true }
allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Geo).to receive(:license_allows?) { true }
allow(Gitlab::Database).to receive(:readonly?) { true }
end
it 'does not give access to upload wiki code' do
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "You can't push code to a secondary GitLab Geo node.")
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "You can't push code to a read-only GitLab instance.")
end
end
end
......
require 'spec_helper'
describe Gitlab::Middleware::ReadonlyGeo do
describe Gitlab::Middleware::Readonly do
include Rack::Test::Methods
RSpec::Matchers.define :be_a_redirect do
......@@ -38,11 +38,11 @@ describe Gitlab::Middleware::ReadonlyGeo do
let(:request) { Rack::MockRequest.new(rack_stack) }
context 'normal requests to a secondary Gitlab Geo' do
context 'normal requests to a readonly Gitlab instance' do
let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] } }
before do
allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Database).to receive(:readonly?) { true }
end
it 'expects PATCH requests to be disallowed' do
......@@ -98,13 +98,6 @@ describe Gitlab::Middleware::ReadonlyGeo do
expect(subject).not_to disallow_request
end
it 'expects a GET status request to be allowed' do
response = request.get("/api/#{API::API.version}/geo/status")
expect(response).not_to be_a_redirect
expect(subject).not_to disallow_request
end
it 'expects a POST LFS request to batch URL to be allowed' do
response = request.post('/root/rouge.git/info/lfs/objects/batch')
......@@ -114,12 +107,12 @@ describe Gitlab::Middleware::ReadonlyGeo do
end
end
context 'json requests to a secondary Geo node' do
context 'json requests to a read-only GitLab instance' do
let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'application/json' }, ['OK']] } }
let(:content_json) { { 'CONTENT_TYPE' => 'application/json' } }
before do
allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Database).to receive(:readonly?) { true }
end
it 'expects PATCH requests to be disallowed' do
......
......@@ -12,10 +12,10 @@ describe Group, 'Routable' do
it { is_expected.to have_many(:redirect_routes).dependent(:destroy) }
end
describe 'Geo secondary' do
describe 'GitLab read-only instance' do
it 'does not save route if route is not present' do
group.route.path = ''
allow(Gitlab::Geo).to receive(:secondary?).and_return(true)
allow(Gitlab::Database).to receive(:readonly?).and_return(true)
expect(group).to receive(:update_route).and_call_original
expect { group.full_path }.to change { Route.count }.by(0)
......
......@@ -822,8 +822,8 @@ describe Project do
end.to change { project.has_external_issue_tracker}.to(false)
end
it 'does not cache data when in a secondary gitlab geo node' do
allow(Gitlab::Geo).to receive(:secondary?) { true }
it 'does not cache data when in a read-only GitLab instance' do
allow(Gitlab::Database).to receive(:readonly?) { true }
expect do
project.cache_has_external_issue_tracker
......@@ -852,8 +852,8 @@ describe Project do
end.to change { project.has_external_wiki}.to(false)
end
it 'does not cache data when in a secondary gitlab geo node' do
allow(Gitlab::Geo).to receive(:secondary?) { true }
it 'does not cache data when in a read-only GitLab instance' do
allow(Gitlab::Database).to receive(:readonly?) { true }
expect do
project.cache_has_external_wiki
......
......@@ -887,7 +887,7 @@ describe 'Git LFS API and storage' do
end
before do
allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Database).to receive(:readonly?) { true }
project.team << [user, :master]
enable_lfs
end
......@@ -902,7 +902,7 @@ describe 'Git LFS API and storage' do
post_lfs_json path, body.merge('operation' => 'upload'), headers
expect(response).to have_gitlab_http_status(403)
expect(json_response).to include('message' => "You cannot write to a secondary GitLab Geo instance. Please use #{project.http_url_to_repo} instead.")
expect(json_response).to include('message' => 'You cannot write to this read-only GitLab instance.')
end
end
......
......@@ -39,9 +39,9 @@ describe Users::ActivityService do
end
end
context 'when in Geo secondary node' do
context 'when in GitLab read-only instance' do
before do
allow(Gitlab::Geo).to receive(:secondary?).and_return(true)
allow(Gitlab::Database).to receive(:readonly?).and_return(true)
end
it 'does not update last_activity_at' do
......
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