Commit 21419046 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2018-05-28' into 'master'

CE upstream - 2018-05-28 12:42 UTC

Closes gitaly#1194

See merge request gitlab-org/gitlab-ee!5870
parents 03b30da5 7f32c287
...@@ -172,7 +172,7 @@ gem 'acts-as-taggable-on', '~> 5.0' ...@@ -172,7 +172,7 @@ gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs # Background jobs
gem 'sidekiq', '~> 5.1' gem 'sidekiq', '~> 5.1'
gem 'sidekiq-cron', '~> 0.6.0' gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.5.2' gem 'redis-namespace', '~> 1.6.0'
gem 'sidekiq-limit_fetch', '~> 3.4', require: false gem 'sidekiq-limit_fetch', '~> 3.4', require: false
# Cron Parser # Cron Parser
...@@ -427,7 +427,7 @@ group :ed25519 do ...@@ -427,7 +427,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.99.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.100.0', require: 'gitaly'
gem 'grpc', '~> 1.11.0' gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed # Locked until https://github.com/google/protobuf/issues/4210 is closed
......
...@@ -305,7 +305,7 @@ GEM ...@@ -305,7 +305,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (0.99.0) gitaly-proto (0.100.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.10) grpc (~> 1.10)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -738,8 +738,8 @@ GEM ...@@ -738,8 +738,8 @@ GEM
redis-activesupport (5.0.4) redis-activesupport (5.0.4)
activesupport (>= 3, < 6) activesupport (>= 3, < 6)
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-namespace (1.5.2) redis-namespace (1.6.0)
redis (~> 3.0, >= 3.0.4) redis (>= 3.0.4)
redis-rack (2.0.4) redis-rack (2.0.4)
rack (>= 1.5, < 3) rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
...@@ -1070,7 +1070,7 @@ DEPENDENCIES ...@@ -1070,7 +1070,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.99.0) gitaly-proto (~> 0.100.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
...@@ -1166,7 +1166,7 @@ DEPENDENCIES ...@@ -1166,7 +1166,7 @@ DEPENDENCIES
recaptcha (~> 3.0) recaptcha (~> 3.0)
redcarpet (~> 3.4) redcarpet (~> 3.4)
redis (~> 3.2) redis (~> 3.2)
redis-namespace (~> 1.5.2) redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2) redis-rails (~> 5.0.2)
request_store (~> 1.3) request_store (~> 1.3)
responders (~> 2.0) responders (~> 2.0)
......
...@@ -3,7 +3,11 @@ class Admin::DashboardController < Admin::ApplicationController ...@@ -3,7 +3,11 @@ class Admin::DashboardController < Admin::ApplicationController
include CountHelper include CountHelper
COUNTED_ITEMS = [Project, User, Group, ForkedProjectLink, Issue, MergeRequest,
Note, Snippet, Key, Milestone].freeze
def index def index
@counts = Gitlab::Database::Count.approximate_counts(COUNTED_ITEMS)
@projects = Project.order_id_desc.without_deleted.with_route.limit(10) @projects = Project.order_id_desc.without_deleted.with_route.limit(10)
@users = User.order_id_desc.limit(10) @users = User.order_id_desc.limit(10)
@groups = Group.order_id_desc.with_route.limit(10) @groups = Group.order_id_desc.with_route.limit(10)
......
module CountHelper module CountHelper
def approximate_count_with_delimiters(model) def approximate_count_with_delimiters(count_data, model)
number_with_delimiter(Gitlab::Database::Count.approximate_count(model)) count = count_data[model]
raise "Missing model #{model} from count data" unless count
number_with_delimiter(count)
end end
end end
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
= link_to admin_projects_path do = link_to admin_projects_path do
%h3.text-center %h3.text-center
Projects: Projects:
= approximate_count_with_delimiters(Project) = approximate_count_with_delimiters(@counts, Project)
%hr %hr
= link_to('New project', new_project_path, class: "btn btn-new") = link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4 .col-sm-4
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= link_to admin_users_path do = link_to admin_users_path do
%h3.text-center %h3.text-center
Users: Users:
= approximate_count_with_delimiters(User) = approximate_count_with_delimiters(@counts, User)
= render_if_exists 'users_statistics' = render_if_exists 'users_statistics'
%hr %hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new" = link_to 'New user', new_admin_user_path, class: "btn btn-new"
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
= link_to admin_groups_path do = link_to admin_groups_path do
%h3.text-center %h3.text-center
Groups: Groups:
= approximate_count_with_delimiters(Group) = approximate_count_with_delimiters(@counts, Group)
%hr %hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new" = link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row .row
...@@ -42,31 +42,31 @@ ...@@ -42,31 +42,31 @@
%p %p
Forks Forks
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(ForkedProjectLink) = approximate_count_with_delimiters(@counts, ForkedProjectLink)
%p %p
Issues Issues
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Issue) = approximate_count_with_delimiters(@counts, Issue)
%p %p
Merge Requests Merge Requests
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(MergeRequest) = approximate_count_with_delimiters(@counts, MergeRequest)
%p %p
Notes Notes
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Note) = approximate_count_with_delimiters(@counts, Note)
%p %p
Snippets Snippets
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Snippet) = approximate_count_with_delimiters(@counts, Snippet)
%p %p
SSH Keys SSH Keys
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Key) = approximate_count_with_delimiters(@counts, Key)
%p %p
Milestones Milestones
%span.light.float-right %span.light.float-right
= approximate_count_with_delimiters(Milestone) = approximate_count_with_delimiters(@counts, Milestone)
%p %p
Active Users Active Users
%span.light.float-right %span.light.float-right
......
---
title: Update redis-namespace to 1.6.0
merge_request: 19166
author: Takuya Noguchi
type: other
---
title: Add username to terms message in git and API calls
merge_request: 19126
author:
type: changed
---
title: Disallow updating job status if the job is not running
merge_request: 19101
author:
type: fixed
---
title: Fix admin counters not working when PostgreSQL has secondaries
merge_request:
author:
type: fixed
...@@ -539,8 +539,8 @@ gitlab-rake gitlab:db:configure ...@@ -539,8 +539,8 @@ gitlab-rake gitlab:db:configure
> **Note**: If you encounter a `rake aborted!` error stating that PGBouncer is failing to connect to > **Note**: If you encounter a `rake aborted!` error stating that PGBouncer is failing to connect to
PostgreSQL it may be that your PGBouncer node's IP address is missing from PostgreSQL it may be that your PGBouncer node's IP address is missing from
PostgreSQL's `trust_auth_cidr_addresses` in `gitlab.rb` on your database nodes. See PostgreSQL's `trust_auth_cidr_addresses` in `gitlab.rb` on your database nodes. See
[PGBouncer error `ERROR: pgbouncer cannot connect to server`](#pgbouncer-error-error-pgbouncer-cannot-connect-to-server) [PGBouncer error `ERROR: pgbouncer cannot connect to server`](#pgbouncer-error-error-pgbouncer-cannot-connect-to-server)
in the Troubleshooting section before proceeding. in the Troubleshooting section before proceeding.
#### Ensure GitLab is running #### Ensure GitLab is running
...@@ -974,25 +974,25 @@ On the consul server nodes, it is important to restart the consul service in a c ...@@ -974,25 +974,25 @@ On the consul server nodes, it is important to restart the consul service in a c
#### PGBouncer error `ERROR: pgbouncer cannot connect to server` #### PGBouncer error `ERROR: pgbouncer cannot connect to server`
You may get this error when running `gitlab-rake gitlab:db:configure` or you You may get this error when running `gitlab-rake gitlab:db:configure` or you
may see the error in the PGBouncer log file. may see the error in the PGBouncer log file.
``` ```
PG::ConnectionBad: ERROR: pgbouncer cannot connect to server PG::ConnectionBad: ERROR: pgbouncer cannot connect to server
``` ```
The problem may be that your PGBouncer node's IP address is not included in the The problem may be that your PGBouncer node's IP address is not included in the
`trust_auth_cidr_addresses` setting in `/etc/gitlab/gitlab.rb` on the database nodes. `trust_auth_cidr_addresses` setting in `/etc/gitlab/gitlab.rb` on the database nodes.
You can confirm that this is the issue by checking the PostgreSQL log on the master You can confirm that this is the issue by checking the PostgreSQL log on the master
database node. If you see the following error then `trust_auth_cidr_addresses` database node. If you see the following error then `trust_auth_cidr_addresses`
is the problem. is the problem.
``` ```
2018-03-29_13:59:12.11776 FATAL: no pg_hba.conf entry for host "123.123.123.123", user "pgbouncer", database "gitlabhq_production", SSL off 2018-03-29_13:59:12.11776 FATAL: no pg_hba.conf entry for host "123.123.123.123", user "pgbouncer", database "gitlabhq_production", SSL off
``` ```
To fix the problem, add the IP address to `/etc/gitlab/gitlab.rb`. To fix the problem, add the IP address to `/etc/gitlab/gitlab.rb`.
``` ```
postgresql['trust_auth_cidr_addresses'] = %w(123.123.123.123/32 <other_cidrs>) postgresql['trust_auth_cidr_addresses'] = %w(123.123.123.123/32 <other_cidrs>)
......
...@@ -77,24 +77,23 @@ for each GitLab application server in your environment. ...@@ -77,24 +77,23 @@ for each GitLab application server in your environment.
In a typical HA setup, this will be the url of the load balancer which will In a typical HA setup, this will be the url of the load balancer which will
route traffic to all GitLab application servers in the HA cluster. route traffic to all GitLab application servers in the HA cluster.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration. > **Note:** When you specify `https` in the `external_url`, as in the example
above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
certificates are not present, Nginx will fail to start. See
[Nginx documentation](http://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
for more information.
## First GitLab application server ## First GitLab application server
As a final step, run the setup rake task on the first GitLab application server. As a final step, run the setup rake task **only on** the first GitLab application server.
It is not necessary to run this on additional application servers. Do not run this on additional application servers.
1. Initialize the database by running `sudo gitlab-rake gitlab:setup`. 1. Initialize the database by running `sudo gitlab-rake gitlab:setup`.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
> **WARNING:** Only run this setup task on **NEW** GitLab instances because it > **WARNING:** Only run this setup task on **NEW** GitLab instances because it
will wipe any existing data. will wipe any existing data.
> **Note:** When you specify `https` in the `external_url`, as in the example
above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
certificates are not present, Nginx will fail to start. See
[Nginx documentation](http://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
for more information.
## Extra configuration for additional GitLab application servers ## Extra configuration for additional GitLab application servers
Additional GitLab servers (servers configured **after** the first GitLab server) Additional GitLab servers (servers configured **after** the first GitLab server)
...@@ -105,15 +104,23 @@ need some extra configuration. ...@@ -105,15 +104,23 @@ need some extra configuration.
secondary servers **prior to** running the first `reconfigure` in the steps secondary servers **prior to** running the first `reconfigure` in the steps
above. above.
```ruby
gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860'
gitlab_rails['otp_key_base'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa'
gitlab_rails['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d'
gitlab_rails['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964'
```
1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations 1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
from running on upgrade. Only the primary GitLab application server should from running on upgrade. Only the primary GitLab application server should
handle migrations. handle migrations.
1. **Optional** Configure host keys. Copy all contents(primary and public keys) inside `/etc/ssh/` on 1. **Optional** Configure host keys. Copy all contents(primary and public keys) inside `/etc/ssh/` on
the primary application server to `/ets/ssh` on all secondary servers. This the primary application server to `/ets/ssh` on all secondary servers. This
prevents false man-in-the-middle-attack alerts when accesing servers in your prevents false man-in-the-middle-attack alerts when accessing servers in your
High Availability cluster behind a load balancer. High Availability cluster behind a load balancer.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
## Troubleshooting ## Troubleshooting
......
...@@ -123,6 +123,7 @@ module API ...@@ -123,6 +123,7 @@ module API
end end
put '/:id' do put '/:id' do
job = authenticate_job! job = authenticate_job!
forbidden!('Job is not running') unless job.running?
job.trace.set(params[:trace]) if params[:trace] job.trace.set(params[:trace]) if params[:trace]
...@@ -131,9 +132,9 @@ module API ...@@ -131,9 +132,9 @@ module API
case params[:state].to_s case params[:state].to_s
when 'success' when 'success'
job.success job.success!
when 'failed' when 'failed'
job.drop(params[:failure_reason] || :unknown_failure) job.drop!(params[:failure_reason] || :unknown_failure)
end end
end end
......
...@@ -73,29 +73,40 @@ module Backup ...@@ -73,29 +73,40 @@ module Backup
end end
def prepare_directories def prepare_directories
# TODO: Need to find a way to do this for gitaly
# Gitaly discussion issue: https://gitlab.com/gitlab-org/gitaly/issues/1194
Gitlab.config.repositories.storages.each do |name, repository_storage| Gitlab.config.repositories.storages.each do |name, repository_storage|
path = repository_storage.legacy_disk_path delete_all_repositories(name, repository_storage)
next unless File.exist?(path) end
end
# Move all files in the existing repos directory except . and .. to
# repositories.old.<timestamp> directory def delete_all_repositories(name, repository_storage)
bk_repos_path = File.join(Gitlab.config.backup.path, "tmp", "#{name}-repositories.old." + Time.now.to_i.to_s) gitaly_migrate(:delete_all_repositories) do |is_enabled|
FileUtils.mkdir_p(bk_repos_path, mode: 0700) if is_enabled
files = Dir.glob(File.join(path, "*"), File::FNM_DOTMATCH) - [File.join(path, "."), File.join(path, "..")] Gitlab::GitalyClient::StorageService.new(name).delete_all_repositories
else
begin local_delete_all_repositories(name, repository_storage)
FileUtils.mv(files, bk_repos_path)
rescue Errno::EACCES
access_denied_error(path)
rescue Errno::EBUSY
resource_busy_error(path)
end end
end end
end end
def local_delete_all_repositories(name, repository_storage)
path = repository_storage.legacy_disk_path
return unless File.exist?(path)
# Move all files in the existing repos directory except . and .. to
# repositories.old.<timestamp> directory
bk_repos_path = File.join(Gitlab.config.backup.path, "tmp", "#{name}-repositories.old." + Time.now.to_i.to_s)
FileUtils.mkdir_p(bk_repos_path, mode: 0700)
files = Dir.glob(File.join(path, "*"), File::FNM_DOTMATCH) - [File.join(path, "."), File.join(path, "..")]
begin
FileUtils.mv(files, bk_repos_path)
rescue Errno::EACCES
access_denied_error(path)
rescue Errno::EBUSY
resource_busy_error(path)
end
end
def restore_custom_hooks(project) def restore_custom_hooks(project)
# TODO: Need to find a way to do this for gitaly # TODO: Need to find a way to do this for gitaly
# Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/1195 # Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/1195
...@@ -113,6 +124,7 @@ module Backup ...@@ -113,6 +124,7 @@ module Backup
def restore def restore
prepare_directories prepare_directories
gitlab_shell = Gitlab::Shell.new gitlab_shell = Gitlab::Shell.new
Project.find_each(batch_size: 1000) do |project| Project.find_each(batch_size: 1000) do |project|
progress.print " * #{project.full_path} ... " progress.print " * #{project.full_path} ... "
path_to_project_bundle = path_to_bundle(project) path_to_project_bundle = path_to_bundle(project)
...@@ -121,7 +133,6 @@ module Backup ...@@ -121,7 +133,6 @@ module Backup
restore_repo_success = nil restore_repo_success = nil
if File.exist?(path_to_project_bundle) if File.exist?(path_to_project_bundle)
begin begin
gitlab_shell.remove_repository(project.repository_storage, project.disk_path) if project.repository_exists?
project.repository.create_from_bundle path_to_project_bundle project.repository.create_from_bundle path_to_project_bundle
restore_repo_success = true restore_repo_success = true
rescue => e rescue => e
...@@ -146,7 +157,6 @@ module Backup ...@@ -146,7 +157,6 @@ module Backup
if File.exist?(path_to_wiki_bundle) if File.exist?(path_to_wiki_bundle)
progress.print " * #{wiki.full_path} ... " progress.print " * #{wiki.full_path} ... "
begin begin
gitlab_shell.remove_repository(wiki.repository_storage, wiki.disk_path) if wiki.repository_exists?
wiki.repository.create_from_bundle(path_to_wiki_bundle) wiki.repository.create_from_bundle(path_to_wiki_bundle)
progress.puts "[DONE]".color(:green) progress.puts "[DONE]".color(:green)
rescue => e rescue => e
...@@ -224,5 +234,11 @@ module Backup ...@@ -224,5 +234,11 @@ module Backup
def display_repo_path(project) def display_repo_path(project)
project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path
end end
def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block)
Gitlab::GitalyClient.migrate(method, status: status, &block)
rescue GRPC::NotFound, GRPC::BadStatus => e
raise Error, e
end
end end
end end
...@@ -8,12 +8,12 @@ module Gitlab ...@@ -8,12 +8,12 @@ module Gitlab
def rejection_message def rejection_message
case rejection_type case rejection_type
when :internal when :internal
'This action cannot be performed by internal users' "This action cannot be performed by internal users"
when :terms_not_accepted when :terms_not_accepted
'You must accept the Terms of Service in order to perform this action. '\ "You (#{@user.to_reference}) must accept the Terms of Service in order to perform this action. "\
'Please access GitLab from a web browser to accept these terms.' "Please access GitLab from a web browser to accept these terms."
else else
'Your account has been blocked.' "Your account has been blocked."
end end
end end
......
...@@ -17,31 +17,69 @@ module Gitlab ...@@ -17,31 +17,69 @@ module Gitlab
].freeze ].freeze
end end
def self.approximate_count(model) # Takes in an array of models and returns a Hash for the approximate
return model.count unless Gitlab::Database.postgresql? # counts for them. If the model's table has not been vacuumed or
# analyzed recently, simply run the Model.count to get the data.
#
# @param [Array]
# @return [Hash] of Model -> count mapping
def self.approximate_counts(models)
table_to_model_map = models.each_with_object({}) do |model, hash|
hash[model.table_name] = model
end
execute_estimate_if_updated_recently(model) || model.count table_names = table_to_model_map.keys
end counts_by_table_name = Gitlab::Database.postgresql? ? reltuples_from_recently_updated(table_names) : {}
def self.execute_estimate_if_updated_recently(model) # Convert table -> count to Model -> count
ActiveRecord::Base.connection.select_value(postgresql_estimate_query(model)).to_i if reltuples_updated_recently?(model) counts_by_model = counts_by_table_name.each_with_object({}) do |pair, hash|
rescue *CONNECTION_ERRORS model = table_to_model_map[pair.first]
hash[model] = pair.second
end
missing_tables = table_names - counts_by_table_name.keys
missing_tables.each do |table|
model = table_to_model_map[table]
counts_by_model[model] = model.count
end
counts_by_model
end end
def self.reltuples_updated_recently?(model) # Returns a hash of the table names that have recently updated tuples.
time = "to_timestamp(#{1.hour.ago.to_i})" #
query = <<~SQL # @param [Array] table names
SELECT 1 FROM pg_stat_user_tables WHERE relname = '#{model.table_name}' AND # @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 })
(last_vacuum > #{time} OR last_autovacuum > #{time} OR last_analyze > #{time} OR last_autoanalyze > #{time}) def self.reltuples_from_recently_updated(table_names)
SQL query = postgresql_estimate_query(table_names)
rows = []
ActiveRecord::Base.connection.select_all(query).count > 0 # Querying tuple stats only works on the primary. Due to load
# balancing, we need to ensure this query hits the load balancer. The
# easiest way to do this is to start a transaction.
ActiveRecord::Base.transaction do
rows = ActiveRecord::Base.connection.select_all(query)
end
rows.each_with_object({}) { |row, data| data[row['table_name']] = row['estimate'].to_i }
rescue *CONNECTION_ERRORS rescue *CONNECTION_ERRORS
false {}
end end
def self.postgresql_estimate_query(model) # Generates the PostgreSQL query to return the tuples for tables
"SELECT reltuples::bigint AS estimate FROM pg_class where relname = '#{model.table_name}'" # that have been vacuumed or analyzed in the last hour.
#
# @param [Array] table names
# @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 })
def self.postgresql_estimate_query(table_names)
time = "to_timestamp(#{1.hour.ago.to_i})"
<<~SQL
SELECT pg_class.relname AS table_name, reltuples::bigint AS estimate FROM pg_class
LEFT JOIN pg_stat_user_tables ON pg_class.relname = pg_stat_user_tables.relname
WHERE pg_class.relname IN (#{table_names.map { |table| "'#{table}'" }.join(',')})
AND (last_vacuum > #{time} OR last_autovacuum > #{time} OR last_analyze > #{time} OR last_autoanalyze > #{time})
SQL
end end
end end
end end
......
module Gitlab
module GitalyClient
class StorageService
def initialize(storage)
@storage = storage
end
# Delete all repositories in the storage. This is a slow and VERY DESTRUCTIVE operation.
def delete_all_repositories
request = Gitaly::DeleteAllRepositoriesRequest.new(storage_name: @storage)
GitalyClient.call(@storage, :storage_service, :delete_all_repositories, request)
end
end
end
end
...@@ -71,6 +71,40 @@ describe Backup::Repository do ...@@ -71,6 +71,40 @@ describe Backup::Repository do
end end
end end
describe '#delete_all_repositories', :seed_helper do
shared_examples('delete_all_repositories') do
before do
allow(FileUtils).to receive(:mkdir_p).and_call_original
allow(FileUtils).to receive(:mv).and_call_original
end
after(:all) do
ensure_seeds
end
it 'removes all repositories' do
# Sanity check: there should be something for us to delete
expect(list_repositories).to include(File.join(SEED_STORAGE_PATH, TEST_REPO_PATH))
subject.delete_all_repositories('default', Gitlab.config.repositories.storages['default'])
expect(list_repositories).to be_empty
end
def list_repositories
Dir[SEED_STORAGE_PATH + '/*.git']
end
end
context 'with gitaly' do
it_behaves_like 'delete_all_repositories'
end
context 'without gitaly', :skip_gitaly_mock do
it_behaves_like 'delete_all_repositories'
end
end
describe '#empty_repo?' do describe '#empty_repo?' do
context 'for a wiki' do context 'for a wiki' do
let(:wiki) { create(:project_wiki) } let(:wiki) { create(:project_wiki) }
......
...@@ -22,7 +22,8 @@ describe Gitlab::Auth::UserAccessDeniedReason do ...@@ -22,7 +22,8 @@ describe Gitlab::Auth::UserAccessDeniedReason do
enforce_terms enforce_terms
end end
it { is_expected.to match /You must accept the Terms of Service/ } it { is_expected.to match /must accept the Terms of Service/ }
it { is_expected.to include(user.username) }
end end
context 'when the user is internal' do context 'when the user is internal' do
......
...@@ -3,59 +3,68 @@ require 'spec_helper' ...@@ -3,59 +3,68 @@ require 'spec_helper'
describe Gitlab::Database::Count do describe Gitlab::Database::Count do
before do before do
create_list(:project, 3) create_list(:project, 3)
create(:identity)
end end
describe '.execute_estimate_if_updated_recently', :postgresql do let(:models) { [Project, Identity] }
context 'when reltuples have not been updated' do
before do
expect(described_class).to receive(:reltuples_updated_recently?).and_return(false)
end
it 'returns nil' do describe '.approximate_counts' do
expect(described_class.execute_estimate_if_updated_recently(Project)).to be nil context 'with MySQL' do
end context 'when reltuples have not been updated' do
end it 'counts all models the normal way' do
expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
context 'when reltuples have been updated' do expect(Project).to receive(:count).and_call_original
before do expect(Identity).to receive(:count).and_call_original
ActiveRecord::Base.connection.execute('ANALYZE projects')
end
it 'calls postgresql_estimate_query' do expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 })
expect(described_class).to receive(:postgresql_estimate_query).with(Project).and_call_original end
expect(described_class.execute_estimate_if_updated_recently(Project)).to eq(3)
end end
end end
end
describe '.approximate_count' do context 'with PostgreSQL', :postgresql do
context 'when reltuples have not been updated' do describe 'when reltuples have not been updated' do
it 'counts all projects the normal way' do it 'counts all models the normal way' do
allow(described_class).to receive(:reltuples_updated_recently?).and_return(false) expect(described_class).to receive(:reltuples_from_recently_updated).with(%w(projects identities)).and_return({})
expect(Project).to receive(:count).and_call_original expect(Project).to receive(:count).and_call_original
expect(described_class.approximate_count(Project)).to eq(3) expect(Identity).to receive(:count).and_call_original
expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 })
end
end end
end
context 'no permission' do describe 'no permission' do
it 'falls back to standard query' do it 'falls back to standard query' do
allow(described_class).to receive(:reltuples_updated_recently?).and_raise(PG::InsufficientPrivilege) allow(described_class).to receive(:postgresql_estimate_query).and_raise(PG::InsufficientPrivilege)
expect(Project).to receive(:count).and_call_original expect(Project).to receive(:count).and_call_original
expect(described_class.approximate_count(Project)).to eq(3) expect(Identity).to receive(:count).and_call_original
expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 })
end
end end
end
describe 'when reltuples have been updated', :postgresql do describe 'when some reltuples have been updated' do
before do it 'counts projects in the fast way' do
ActiveRecord::Base.connection.execute('ANALYZE projects') expect(described_class).to receive(:reltuples_from_recently_updated).with(%w(projects identities)).and_return({ 'projects' => 3 })
expect(Project).not_to receive(:count).and_call_original
expect(Identity).to receive(:count).and_call_original
expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 })
end
end end
it 'counts all projects in the fast way' do describe 'when all reltuples have been updated' do
expect(described_class).to receive(:postgresql_estimate_query).with(Project).and_call_original before do
ActiveRecord::Base.connection.execute('ANALYZE projects')
ActiveRecord::Base.connection.execute('ANALYZE identities')
end
it 'counts models with the standard way' do
expect(Project).not_to receive(:count)
expect(Identity).not_to receive(:count)
expect(described_class.approximate_count(Project)).to eq(3) expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 })
end
end end
end end
end end
......
...@@ -1183,7 +1183,7 @@ describe Gitlab::GitAccess do ...@@ -1183,7 +1183,7 @@ describe Gitlab::GitAccess do
it 'blocks access when the user did not accept terms', :aggregate_failures do it 'blocks access when the user did not accept terms', :aggregate_failures do
actions.each do |action| actions.each do |action|
expect { action.call }.to raise_unauthorized(/You must accept the Terms of Service in order to perform this action/) expect { action.call }.to raise_unauthorized(/must accept the Terms of Service in order to perform this action/)
end end
end end
......
...@@ -174,7 +174,7 @@ describe API::Helpers do ...@@ -174,7 +174,7 @@ describe API::Helpers do
end end
it 'returns a 403 when a user has not accepted the terms' do it 'returns a 403 when a user has not accepted the terms' do
expect { current_user }.to raise_error /You must accept the Terms of Service/ expect { current_user }.to raise_error /must accept the Terms of Service/
end end
it 'sets the current user when the user accepted the terms' do it 'sets the current user when the user accepted the terms' do
......
...@@ -832,6 +832,21 @@ describe API::Runner, :clean_gitlab_redis_shared_state do ...@@ -832,6 +832,21 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end end
end end
context 'when job has already been finished' do
before do
job.trace.set('Job failed')
job.drop!(:script_failure)
end
it 'does not update job status and job trace' do
update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
expect(response).to have_gitlab_http_status(403)
expect(job.trace.raw).to eq 'Job failed'
expect(job).to be_failed
end
end
def update_job(token = job.token, **params) def update_job(token = job.token, **params)
new_params = params.merge(token: token) new_params = params.merge(token: token)
put api("/jobs/#{job.id}"), new_params put api("/jobs/#{job.id}"), new_params
......
...@@ -4,6 +4,11 @@ describe 'admin/dashboard/index.html.haml' do ...@@ -4,6 +4,11 @@ describe 'admin/dashboard/index.html.haml' do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
before do before do
counts = Admin::DashboardController::COUNTED_ITEMS.each_with_object({}) do |item, hash|
hash[item] = 100
end
assign(:counts, counts)
assign(:projects, create_list(:project, 1)) assign(:projects, create_list(:project, 1))
assign(:users, create_list(:user, 1)) assign(:users, create_list(:user, 1))
assign(:groups, create_list(:group, 1)) assign(:groups, create_list(:group, 1))
......
This diff is collapsed.
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