Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
caff5659c9
commit
d229251151
47 changed files with 988 additions and 191 deletions
31
app/models/customer_relations/organization.rb
Normal file
31
app/models/customer_relations/organization.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CustomerRelations::Organization < ApplicationRecord
|
||||
self.table_name = "customer_relations_organizations"
|
||||
|
||||
belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'group_id'
|
||||
|
||||
before_validation :strip_whitespace!
|
||||
|
||||
enum state: {
|
||||
inactive: 0,
|
||||
active: 1
|
||||
}
|
||||
|
||||
validates :group, presence: true
|
||||
validates :name, presence: true
|
||||
validates :name, uniqueness: { case_sensitive: false, scope: [:group_id] }
|
||||
validates :name, length: { maximum: 255 }
|
||||
validates :description, length: { maximum: 1024 }
|
||||
|
||||
def self.find_by_name(group_id, name)
|
||||
where(group: group_id)
|
||||
.where('LOWER(name) = LOWER(?)', name)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def strip_whitespace!
|
||||
name&.strip!
|
||||
end
|
||||
end
|
7
app/models/postgresql/detached_partition.rb
Normal file
7
app/models/postgresql/detached_partition.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Postgresql
|
||||
class DetachedPartition < ApplicationRecord
|
||||
scope :ready_to_drop, -> { where('drop_after < ?', Time.current) }
|
||||
end
|
||||
end
|
|
@ -247,6 +247,15 @@
|
|||
:idempotent: true
|
||||
:tags:
|
||||
- :exclude_from_kubernetes
|
||||
- :name: cronjob:database_drop_detached_partitions
|
||||
:worker_name: Database::DropDetachedPartitionsWorker
|
||||
:feature_category: :database
|
||||
:has_external_dependencies:
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: cronjob:database_partition_management
|
||||
:worker_name: Database::PartitionManagementWorker
|
||||
:feature_category: :database
|
||||
|
|
18
app/workers/database/drop_detached_partitions_worker.rb
Normal file
18
app/workers/database/drop_detached_partitions_worker.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Database
|
||||
class DropDetachedPartitionsWorker
|
||||
include ApplicationWorker
|
||||
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
|
||||
|
||||
feature_category :database
|
||||
data_consistency :always
|
||||
idempotent!
|
||||
|
||||
def perform
|
||||
Gitlab::Database::Partitioning::DetachedPartitionDropper.new.perform
|
||||
ensure
|
||||
Gitlab::Database::Partitioning::PartitionMonitoring.new.report_metrics
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: drop_detached_partitions
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67056
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337155
|
||||
milestone: '14.2'
|
||||
type: development
|
||||
group: group::database
|
||||
default_enabled: false
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: partition_pruning_dry_run
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65093
|
||||
rollout_issue_url:
|
||||
milestone: '14.1'
|
||||
name: partition_pruning
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67056
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337153
|
||||
milestone: '14.2'
|
||||
type: development
|
||||
group: group::database
|
||||
default_enabled: false
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: usage_data_i_testing_full_code_quality_report_total
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49079
|
||||
rollout_issue_url:
|
||||
milestone: '13.8'
|
||||
type: development
|
||||
group: group::testing
|
||||
default_enabled: true
|
|
@ -547,6 +547,9 @@ Settings.cron_jobs['update_container_registry_info_worker']['job_class'] = 'Upda
|
|||
Settings.cron_jobs['postgres_dynamic_partitions_manager'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['postgres_dynamic_partitions_manager']['cron'] ||= '21 */6 * * *'
|
||||
Settings.cron_jobs['postgres_dynamic_partitions_manager']['job_class'] ||= 'Database::PartitionManagementWorker'
|
||||
Settings.cron_jobs['postgres_dynamic_partitions_dropper'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['postgres_dynamic_partitions_dropper']['cron'] ||= '45 12 * * *'
|
||||
Settings.cron_jobs['postgres_dynamic_partitions_dropper']['job_class'] ||= 'Database::DropDetachedPartitionsWorker'
|
||||
Settings.cron_jobs['ci_platform_metrics_update_cron_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['cron'] ||= '47 9 * * *'
|
||||
Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['job_class'] = 'CiPlatformMetricsUpdateCronWorker'
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateDetachedPartitionsTable < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
def change
|
||||
create_table_with_constraints :detached_partitions do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
t.datetime_with_timezone :drop_after, null: false
|
||||
t.text :table_name, null: false
|
||||
|
||||
# Postgres identifier names can be up to 63 bytes
|
||||
# See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
t.text_limit :table_name, 63
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateCustomerRelationsOrganizations < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
def up
|
||||
create_table_with_constraints :customer_relations_organizations do |t|
|
||||
t.references :group, index: false, null: false, foreign_key: { to_table: :namespaces, on_delete: :cascade }
|
||||
t.timestamps_with_timezone null: false
|
||||
t.integer :state, limit: 1, default: 1, null: false
|
||||
t.decimal :default_rate, precision: 18, scale: 2
|
||||
t.text :name, null: false
|
||||
t.text :description
|
||||
|
||||
t.text_limit :name, 255
|
||||
t.text_limit :description, 1024
|
||||
|
||||
t.index 'group_id, LOWER(name)', unique: true, name: :index_customer_relations_organizations_on_unique_name_per_group
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
drop_table :customer_relations_organizations
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,87 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizePushEventPayloadsBigintConversion3 < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
TABLE_NAME = 'push_event_payloads'
|
||||
INDEX_NAME = 'index_push_event_payloads_on_event_id_convert_to_bigint'
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
|
||||
table_name: TABLE_NAME,
|
||||
column_name: 'event_id',
|
||||
job_arguments: [["event_id"], ["event_id_convert_to_bigint"]]
|
||||
)
|
||||
|
||||
return if already_swapped?
|
||||
|
||||
swap_columns
|
||||
end
|
||||
|
||||
def down
|
||||
swap_columns
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def already_swapped?
|
||||
push_event_payloads_columns = columns(TABLE_NAME)
|
||||
event_id = push_event_payloads_columns.find {|c| c.name == 'event_id'}
|
||||
event_id_convert_to_bigint = push_event_payloads_columns.find {|c| c.name == 'event_id_convert_to_bigint'}
|
||||
|
||||
event_id.sql_type == 'bigint' && event_id_convert_to_bigint.sql_type == 'integer'
|
||||
end
|
||||
|
||||
def swap_columns
|
||||
add_concurrent_index TABLE_NAME, :event_id_convert_to_bigint, unique: true, name: INDEX_NAME
|
||||
|
||||
# Add a foreign key on `event_id_convert_to_bigint` before we swap the columns and drop the old FK (fk_36c74129da)
|
||||
add_concurrent_foreign_key TABLE_NAME, :events, column: :event_id_convert_to_bigint,
|
||||
on_delete: :cascade, reverse_lock_order: true
|
||||
|
||||
with_lock_retries(raise_on_exhaustion: true) do
|
||||
# We'll need ACCESS EXCLUSIVE lock on the related tables,
|
||||
# lets make sure it can be acquired from the start.
|
||||
# Lock order should be
|
||||
# 1. events
|
||||
# 2. push_event_payloads
|
||||
# in order to match the order in EventCreateService#create_push_event,
|
||||
# and avoid deadlocks.
|
||||
execute "LOCK TABLE events, #{TABLE_NAME} IN ACCESS EXCLUSIVE MODE"
|
||||
|
||||
# Swap column names
|
||||
temp_name = 'event_id_tmp'
|
||||
execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(:event_id)} TO #{quote_column_name(temp_name)}"
|
||||
execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(:event_id_convert_to_bigint)} TO #{quote_column_name(:event_id)}"
|
||||
execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(temp_name)} TO #{quote_column_name(:event_id_convert_to_bigint)}"
|
||||
|
||||
# We need to update the trigger function in order to make PostgreSQL to
|
||||
# regenerate the execution plan for it. This is to avoid type mismatch errors like
|
||||
# "type of parameter 15 (bigint) does not match that when preparing the plan (integer)"
|
||||
function_name = Gitlab::Database::UnidirectionalCopyTrigger.on_table(TABLE_NAME).name(:event_id, :event_id_convert_to_bigint)
|
||||
execute "ALTER FUNCTION #{quote_table_name(function_name)} RESET ALL"
|
||||
|
||||
# Swap defaults
|
||||
change_column_default TABLE_NAME, :event_id, nil
|
||||
change_column_default TABLE_NAME, :event_id_convert_to_bigint, 0
|
||||
|
||||
# Swap PK constraint
|
||||
execute "ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT push_event_payloads_pkey"
|
||||
rename_index TABLE_NAME, INDEX_NAME, 'push_event_payloads_pkey'
|
||||
execute "ALTER TABLE #{TABLE_NAME} ADD CONSTRAINT push_event_payloads_pkey PRIMARY KEY USING INDEX push_event_payloads_pkey"
|
||||
|
||||
# Drop original FK on the old int4 `event_id` (fk_36c74129da)
|
||||
remove_foreign_key TABLE_NAME, name: concurrent_foreign_key_name(TABLE_NAME, :event_id)
|
||||
# We swapped the columns but the FK for event_id is still using the old name for the event_id_convert_to_bigint column
|
||||
# So we have to also swap the FK name now that we dropped the other one with the same
|
||||
rename_constraint(
|
||||
TABLE_NAME,
|
||||
concurrent_foreign_key_name(TABLE_NAME, :event_id_convert_to_bigint),
|
||||
concurrent_foreign_key_name(TABLE_NAME, :event_id)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
1
db/schema_migrations/20210628154900
Normal file
1
db/schema_migrations/20210628154900
Normal file
|
@ -0,0 +1 @@
|
|||
136a375fbd7e1faf25e7f53e0677b8525811bd917892efa1430d204453bf2a1d
|
1
db/schema_migrations/20210802043253
Normal file
1
db/schema_migrations/20210802043253
Normal file
|
@ -0,0 +1 @@
|
|||
b844c7c56019fc984c2604ae11f6ee9eb587806b5c78e4beea4dda93e384f9b2
|
1
db/schema_migrations/20210804200114
Normal file
1
db/schema_migrations/20210804200114
Normal file
|
@ -0,0 +1 @@
|
|||
db62fb6413db4be5e1013bccf16b0c3a66c9aaf9f3d646f42442be16c511af5f
|
|
@ -12058,6 +12058,28 @@ CREATE SEQUENCE custom_emoji_id_seq
|
|||
|
||||
ALTER SEQUENCE custom_emoji_id_seq OWNED BY custom_emoji.id;
|
||||
|
||||
CREATE TABLE customer_relations_organizations (
|
||||
id bigint NOT NULL,
|
||||
group_id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
state smallint DEFAULT 1 NOT NULL,
|
||||
default_rate numeric(18,2),
|
||||
name text NOT NULL,
|
||||
description text,
|
||||
CONSTRAINT check_2ba9ef1c4c CHECK ((char_length(name) <= 255)),
|
||||
CONSTRAINT check_e476b6058e CHECK ((char_length(description) <= 1024))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE customer_relations_organizations_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE customer_relations_organizations_id_seq OWNED BY customer_relations_organizations.id;
|
||||
|
||||
CREATE TABLE dast_profile_schedules (
|
||||
id bigint NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
|
@ -12521,6 +12543,24 @@ CREATE SEQUENCE design_user_mentions_id_seq
|
|||
|
||||
ALTER SEQUENCE design_user_mentions_id_seq OWNED BY design_user_mentions.id;
|
||||
|
||||
CREATE TABLE detached_partitions (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
drop_after timestamp with time zone NOT NULL,
|
||||
table_name text NOT NULL,
|
||||
CONSTRAINT check_aecee24ba3 CHECK ((char_length(table_name) <= 63))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE detached_partitions_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE detached_partitions_id_seq OWNED BY detached_partitions.id;
|
||||
|
||||
CREATE TABLE diff_note_positions (
|
||||
id bigint NOT NULL,
|
||||
note_id bigint NOT NULL,
|
||||
|
@ -17574,7 +17614,7 @@ ALTER SEQUENCE protected_tags_id_seq OWNED BY protected_tags.id;
|
|||
|
||||
CREATE TABLE push_event_payloads (
|
||||
commit_count bigint NOT NULL,
|
||||
event_id integer NOT NULL,
|
||||
event_id_convert_to_bigint integer DEFAULT 0 NOT NULL,
|
||||
action smallint NOT NULL,
|
||||
ref_type smallint NOT NULL,
|
||||
commit_from bytea,
|
||||
|
@ -17582,7 +17622,7 @@ CREATE TABLE push_event_payloads (
|
|||
ref text,
|
||||
commit_title character varying(70),
|
||||
ref_count integer,
|
||||
event_id_convert_to_bigint bigint DEFAULT 0 NOT NULL
|
||||
event_id bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE push_rules (
|
||||
|
@ -20179,6 +20219,8 @@ ALTER TABLE ONLY csv_issue_imports ALTER COLUMN id SET DEFAULT nextval('csv_issu
|
|||
|
||||
ALTER TABLE ONLY custom_emoji ALTER COLUMN id SET DEFAULT nextval('custom_emoji_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY customer_relations_organizations ALTER COLUMN id SET DEFAULT nextval('customer_relations_organizations_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY dast_profile_schedules ALTER COLUMN id SET DEFAULT nextval('dast_profile_schedules_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY dast_profiles ALTER COLUMN id SET DEFAULT nextval('dast_profiles_id_seq'::regclass);
|
||||
|
@ -20217,6 +20259,8 @@ ALTER TABLE ONLY design_management_versions ALTER COLUMN id SET DEFAULT nextval(
|
|||
|
||||
ALTER TABLE ONLY design_user_mentions ALTER COLUMN id SET DEFAULT nextval('design_user_mentions_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY detached_partitions ALTER COLUMN id SET DEFAULT nextval('detached_partitions_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY diff_note_positions ALTER COLUMN id SET DEFAULT nextval('diff_note_positions_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY dora_daily_metrics ALTER COLUMN id SET DEFAULT nextval('dora_daily_metrics_id_seq'::regclass);
|
||||
|
@ -21458,6 +21502,9 @@ ALTER TABLE ONLY csv_issue_imports
|
|||
ALTER TABLE ONLY custom_emoji
|
||||
ADD CONSTRAINT custom_emoji_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY customer_relations_organizations
|
||||
ADD CONSTRAINT customer_relations_organizations_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY dast_profile_schedules
|
||||
ADD CONSTRAINT dast_profile_schedules_pkey PRIMARY KEY (id);
|
||||
|
||||
|
@ -21533,6 +21580,9 @@ ALTER TABLE ONLY design_management_versions
|
|||
ALTER TABLE ONLY design_user_mentions
|
||||
ADD CONSTRAINT design_user_mentions_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY detached_partitions
|
||||
ADD CONSTRAINT detached_partitions_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY diff_note_positions
|
||||
ADD CONSTRAINT diff_note_positions_pkey PRIMARY KEY (id);
|
||||
|
||||
|
@ -23557,6 +23607,8 @@ CREATE INDEX index_custom_emoji_on_creator_id ON custom_emoji USING btree (creat
|
|||
|
||||
CREATE UNIQUE INDEX index_custom_emoji_on_namespace_id_and_name ON custom_emoji USING btree (namespace_id, name);
|
||||
|
||||
CREATE UNIQUE INDEX index_customer_relations_organizations_on_unique_name_per_group ON customer_relations_organizations USING btree (group_id, lower(name));
|
||||
|
||||
CREATE UNIQUE INDEX index_cycle_analytics_stage_event_hashes_on_hash_sha_256 ON analytics_cycle_analytics_stage_event_hashes USING btree (hash_sha256);
|
||||
|
||||
CREATE UNIQUE INDEX index_daily_build_group_report_results_unique_columns ON ci_daily_build_group_report_results USING btree (project_id, ref_path, date, group_name);
|
||||
|
@ -27801,6 +27853,9 @@ ALTER TABLE ONLY jira_connect_subscriptions
|
|||
ALTER TABLE ONLY fork_network_members
|
||||
ADD CONSTRAINT fk_rails_a40860a1ca FOREIGN KEY (fork_network_id) REFERENCES fork_networks(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY customer_relations_organizations
|
||||
ADD CONSTRAINT fk_rails_a48597902f FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY operations_feature_flag_scopes
|
||||
ADD CONSTRAINT fk_rails_a50a04d0a4 FOREIGN KEY (feature_flag_id) REFERENCES operations_feature_flags(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ swap:
|
|||
file name: filename
|
||||
filesystem: file system
|
||||
info: information
|
||||
need to: must
|
||||
repo: repository
|
||||
timezone: time zone
|
||||
utilize: use
|
||||
|
|
|
@ -107,7 +107,7 @@ You can customize the:
|
|||
- SSH remote URL to use the location-aware `git.example.com`. To do so, change the SSH remote URL's
|
||||
host by setting `gitlab_rails['gitlab_ssh_host']` in `gitlab.rb` of web nodes.
|
||||
- HTTP remote URL as shown in
|
||||
[Custom Git clone URL for HTTP(S)](../../../user/admin_area/settings/visibility_and_access_controls.md#custom-git-clone-url-for-https).
|
||||
[Custom Git clone URL for HTTP(S)](../../../user/admin_area/settings/visibility_and_access_controls.md#customize-git-clone-url-for-https).
|
||||
|
||||
## Example Git request handling behavior
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
|
|||
- [Creating users](../user/profile/account/create_accounts.md): Create users manually or through authentication integrations.
|
||||
- [Libravatar](libravatar.md): Use Libravatar instead of Gravatar for user avatars.
|
||||
- [Sign-up restrictions](../user/admin_area/settings/sign_up_restrictions.md): block email addresses of specific domains, or whitelist only specific domains.
|
||||
- [Access restrictions](../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab (SSH, HTTP, HTTPS).
|
||||
- [Access restrictions](../user/admin_area/settings/visibility_and_access_controls.md#configure-enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab (SSH, HTTP, HTTPS).
|
||||
- [Authentication and Authorization](auth/index.md): Configure external authentication with LDAP, SAML, CAS, and additional providers.
|
||||
- [Sync LDAP](auth/ldap/index.md)
|
||||
- [Kerberos authentication](../integration/kerberos.md)
|
||||
|
|
|
@ -28,7 +28,7 @@ They are listed in the public access directory (`/public`) for all users.
|
|||
|
||||
NOTE:
|
||||
By default, `/public` is visible to unauthenticated users. However, if the
|
||||
[**Public** visibility level](../user/admin_area/settings/visibility_and_access_controls.md#restricted-visibility-levels)
|
||||
[**Public** visibility level](../user/admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels)
|
||||
is restricted, `/public` is visible only to signed-in users.
|
||||
|
||||
## Internal projects
|
||||
|
@ -71,7 +71,7 @@ You can restrict the use of visibility levels for users when they create a proje
|
|||
This is useful to prevent users from publicly exposing their repositories by accident. The
|
||||
restricted visibility settings do not apply to admin users.
|
||||
|
||||
For details, see [Restricted visibility levels](../user/admin_area/settings/visibility_and_access_controls.md#restricted-visibility-levels).
|
||||
For details, see [Restricted visibility levels](../user/admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels).
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ You can now see the message on `/help`.
|
|||
|
||||
NOTE:
|
||||
By default, `/help` is visible to unauthenticated users. However, if the
|
||||
[**Public** visibility level](visibility_and_access_controls.md#restricted-visibility-levels)
|
||||
[**Public** visibility level](visibility_and_access_controls.md#restrict-visibility-levels)
|
||||
is restricted, `/help` is visible only to signed-in users.
|
||||
|
||||
## Add a help message to the sign-in page
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.6 KiB |
|
@ -51,7 +51,7 @@ To access the default page for Admin Area settings:
|
|||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| [Repository's custom initial branch name](../../project/repository/branches/default.md#instance-level-custom-initial-branch-name) | Set a custom branch name for new repositories created in your instance. |
|
||||
| [Repository mirror](visibility_and_access_controls.md#allow-mirrors-to-be-set-up-for-projects) | Configure repository mirroring. |
|
||||
| [Repository mirror](visibility_and_access_controls.md#enable-project-mirroring) | Configure repository mirroring. |
|
||||
| [Repository storage](../../../administration/repository_storage_types.md) | Configure storage path settings. |
|
||||
| Repository maintenance | ([Repository checks](../../../administration/repository_checks.md) and [Housekeeping](../../../administration/housekeeping.md)). Configure automatic Git checks and housekeeping on repositories. |
|
||||
| [Repository static objects](../../../administration/static_objects_external_storage.md) | Serve repository static objects (for example, archives and blobs) from an external storage (for example, a CDN). |
|
||||
|
|
|
@ -5,9 +5,10 @@ info: "To determine the technical writer assigned to the Stage/Group associated
|
|||
type: reference
|
||||
---
|
||||
|
||||
# Visibility and access controls **(FREE SELF)**
|
||||
# Control access and visibility **(FREE SELF)**
|
||||
|
||||
GitLab allows administrators to enforce specific controls.
|
||||
GitLab enables users with the [Administrator role](../../permissions.md) to enforce
|
||||
specific controls on branches, projects, snippets, groups, and more.
|
||||
|
||||
To access the visibility and access control options:
|
||||
|
||||
|
@ -16,60 +17,84 @@ To access the visibility and access control options:
|
|||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
|
||||
## Default branch protection
|
||||
## Protect default branches
|
||||
|
||||
This global option defines the branch protection that applies to every repository's
|
||||
[default branch](../../project/repository/branches/default.md).
|
||||
[Branch protection](../../project/protected_branches.md) specifies which roles can push
|
||||
to branches and which roles can delete branches. In this case _Default_ refers to a
|
||||
repository's [default branch](../../project/repository/branches/default.md).
|
||||
With this option, you can define [branch protections](../../project/protected_branches.md)
|
||||
to apply to every repository's [default branch](../../project/repository/branches/default.md).
|
||||
These protections specify the user roles with permission to:
|
||||
|
||||
This setting applies only to each repositories' default branch. To protect other branches, you must configure branch protection in repository. For details, see [protected branches](../../project/protected_branches.md).
|
||||
- Push to branches.
|
||||
- Delete branches.
|
||||
|
||||
To change the default branch protection:
|
||||
This setting applies only to each repository's default branch. To protect other branches,
|
||||
you must configure [branch protection in the repository](../../project/protected_branches.md),
|
||||
or configure [branch protection for groups](../../group/index.md#change-the-default-branch-protection-of-a-group).
|
||||
|
||||
1. Select the desired option.
|
||||
1. Click **Save changes**.
|
||||
To change the default branch protection for the entire instance:
|
||||
|
||||
For more details, see [Protected branches](../../project/protected_branches.md).
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Select a **Default branch protection**:
|
||||
- **Not protected** - Both developers and maintainers can push new commits,
|
||||
force push, or delete the branch.
|
||||
- **Protected against pushes** - Developers cannot push new commits, but are
|
||||
allowed to accept merge requests to the branch. Maintainers can push to the branch.
|
||||
- **Partially protected** - Both developers and maintainers can push new commits,
|
||||
but cannot force push or delete the branch.
|
||||
- **Fully protected** - Developers cannot push new commits, but maintainers can.
|
||||
No one can force push or delete the branch.
|
||||
1. To allow group owners to override the instance's default branch protection, select
|
||||
[**Allow owners to manage default branch protection per group**](#prevent-overrides-of-default-branch-protection).
|
||||
1. Select **Save changes**.
|
||||
|
||||
To change this setting for a specific group, see [Default branch protection for groups](../../group/index.md#change-the-default-branch-protection-of-a-group)
|
||||
|
||||
### Disable group owners from updating default branch protection **(PREMIUM SELF)**
|
||||
### Prevent overrides of default branch protection **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211944) in GitLab 13.0.
|
||||
|
||||
By default, group owners are allowed to override the branch protection set at the global level.
|
||||
Instance-level protections for [default branch](../../project/repository/branches/default.md)
|
||||
can be overridden on a per-group basis by the group's owner. In
|
||||
[GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can
|
||||
disable this privilege for group owners, enforcing the instance-level protection rule:
|
||||
|
||||
In [GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can disable this privilege of group owners.
|
||||
|
||||
To do this:
|
||||
|
||||
1. Uncheck the **Allow owners to manage default branch protection per group** checkbox.
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Deselect the **Allow owners to manage default branch protection per group** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
NOTE:
|
||||
GitLab administrators can still update the default branch protection of a group.
|
||||
|
||||
## Default project creation protection
|
||||
## Define which roles can create projects
|
||||
|
||||
Project creation protection specifies which roles can create projects.
|
||||
Instance-level protections for project creation define which roles can
|
||||
[add projects to a group](../../group/index.md#specify-who-can-add-projects-to-a-group)]
|
||||
on the instance. To alter which roles have permission to create projects:
|
||||
|
||||
To change the default project creation protection:
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. For **Default project creation protection**, select the desired roles:
|
||||
- No one.
|
||||
- Maintainers.
|
||||
- Developers and Maintainers.
|
||||
1. Select **Save changes**.
|
||||
|
||||
1. Select the desired option.
|
||||
1. Click **Save changes**.
|
||||
## Restrict project deletion to Administrators **(PREMIUM SELF)**
|
||||
|
||||
For more details, see [Specify who can add projects to a group](../../group/index.md#specify-who-can-add-projects-to-a-group).
|
||||
Anyone with the **Owner** role, either at the project or group level, can
|
||||
delete a project. To allow only users with the Administrator role to delete projects:
|
||||
|
||||
## Default project deletion protection **(PREMIUM SELF)**
|
||||
|
||||
By default, a project can be deleted by anyone with the **Owner** role, either at the project or
|
||||
group level.
|
||||
|
||||
To ensure only Administrator users can delete projects:
|
||||
|
||||
1. Check the **Default project deletion protection** checkbox.
|
||||
1. Click **Save changes**.
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Scroll to **Default project deletion protection**, and select **Only admins can delete project**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Default delayed project deletion **(PREMIUM SELF)**
|
||||
|
||||
|
@ -104,7 +129,7 @@ To change this period:
|
|||
1. Select the desired option.
|
||||
1. Click **Save changes**.
|
||||
|
||||
### Override default deletion delayed period
|
||||
### Override defaults and delete immediately
|
||||
|
||||
Alternatively, projects that are marked for removal can be deleted immediately. To do so:
|
||||
|
||||
|
@ -112,107 +137,132 @@ Alternatively, projects that are marked for removal can be deleted immediately.
|
|||
1. Delete the project as described in the
|
||||
[Administering Projects page](../../admin_area/#administering-projects).
|
||||
|
||||
## Default project visibility
|
||||
## Configure project visibility defaults
|
||||
|
||||
To set the default visibility levels for new projects:
|
||||
To set the default [visibility levels for new projects](../../../public_access/public_access.md):
|
||||
|
||||
1. Select the desired default project visibility.
|
||||
1. Click **Save changes**.
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Select the desired default project visibility:
|
||||
- **Private** - Project access must be granted explicitly to each user. If this
|
||||
project is part of a group, access will be granted to members of the group.
|
||||
- **Internal** - The project can be accessed by any logged in user except external users.
|
||||
- **Public** - The project can be accessed without any authentication.
|
||||
1. Select **Save changes**.
|
||||
|
||||
For more details on project visibility, see
|
||||
[Project visibility](../../../public_access/public_access.md).
|
||||
## Configure snippet visibility defaults
|
||||
|
||||
## Default snippet visibility
|
||||
|
||||
To set the default visibility levels for new snippets:
|
||||
To set the default visibility levels for new [snippets](../../snippets.md):
|
||||
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Select the desired default snippet visibility.
|
||||
1. Click **Save changes**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
For more details on snippet visibility, see
|
||||
For more details on snippet visibility, read
|
||||
[Project visibility](../../../public_access/public_access.md).
|
||||
|
||||
## Default group visibility
|
||||
## Configure group visibility defaults
|
||||
|
||||
To set the default visibility levels for new groups:
|
||||
|
||||
1. Select the desired default group visibility.
|
||||
1. Click **Save changes**.
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Select the desired default group visibility:
|
||||
- **Private** - The group and its projects can only be viewed by members.
|
||||
- **Internal** - The group and any internal projects can be viewed by any logged in user except external users.
|
||||
- **Public** - The group and any public projects can be viewed without any authentication.
|
||||
1. Select **Save changes**.
|
||||
|
||||
For more details on group visibility, see
|
||||
[Group visibility](../../group/index.md#group-visibility).
|
||||
|
||||
## Restricted visibility levels
|
||||
## Restrict visibility levels
|
||||
|
||||
To set the restricted visibility levels for projects, snippets, and selected pages:
|
||||
To restrict visibility levels for projects, snippets, and selected pages:
|
||||
|
||||
1. Select the desired visibility levels to restrict.
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. In the **Restricted visibility levels** section, select the desired visibility levels to restrict.
|
||||
1. Select **Save changes**.
|
||||
|
||||
For more details on project visibility, see
|
||||
[Project visibility](../../../public_access/public_access.md).
|
||||
|
||||
## Import sources
|
||||
## Configure allowed import sources
|
||||
|
||||
To specify from which hosting sites users can [import their projects](../../project/import/index.md):
|
||||
You can specify from which hosting sites users can [import their projects](../../project/import/index.md):
|
||||
|
||||
1. Check the checkbox beside the name of each hosting site.
|
||||
1. Click **Save changes**.
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Select each of **Import sources** to allow.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Project export
|
||||
## Enable project export
|
||||
|
||||
To enable project export:
|
||||
To enable the export of
|
||||
[projects and their data](../../../user/project/settings/import_export.md#exporting-a-project-and-its-data):
|
||||
|
||||
1. Check the **Project export enabled** checkbox.
|
||||
1. Click **Save changes**.
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Select **Project export enabled**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
For more details, see [Exporting a project and its data](../../../user/project/settings/import_export.md#exporting-a-project-and-its-data).
|
||||
## Configure enabled Git access protocols
|
||||
|
||||
## Enabled Git access protocols
|
||||
|
||||
With GitLab access restrictions, you can select with which protocols users can communicate with
|
||||
GitLab.
|
||||
|
||||
Disabling an access protocol does not block access to the server itself by using those ports. The ports
|
||||
used for the protocol, SSH or HTTP(S), are still accessible. The GitLab restrictions apply at the
|
||||
application level.
|
||||
With GitLab access restrictions, you can select the protocols users can use to
|
||||
communicate with GitLab. Disabling an access protocol does not block port access to the
|
||||
server itself. The ports used for the protocol, SSH or HTTP(S), are still accessible.
|
||||
The GitLab restrictions apply at the application level.
|
||||
|
||||
To specify the enabled Git access protocols:
|
||||
|
||||
1. Select the desired Git access protocols from the dropdown:
|
||||
1. Sign in to GitLab as a user with [Administrator role](../../permissions.md).
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > General**.
|
||||
1. Expand the **Visibility and access controls** section.
|
||||
1. Select the desired Git access protocols:
|
||||
- Both SSH and HTTP(S)
|
||||
- Only SSH
|
||||
- Only HTTP(S)
|
||||
1. Click **Save changes**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
When both SSH and HTTP(S) are enabled, users can choose either protocol.
|
||||
|
||||
When only one protocol is enabled:
|
||||
If only one protocol is enabled:
|
||||
|
||||
- The project page shows only the allowed protocol's URL, with no option to
|
||||
change it.
|
||||
- A tooltip is shown when you hover over the URL's protocol, if an action
|
||||
on the user's part is required. For example, adding an SSH key or setting a password.
|
||||
- GitLab shows a tooltip when you hover over the URL's protocol, if user action
|
||||
(such as adding a SSH key or setting a password) is required:
|
||||
|
||||
![Project URL with SSH only access](img/restricted_url.png)
|
||||
![Project URL with SSH only access](img/restricted_url.png)
|
||||
|
||||
On top of these UI restrictions, GitLab denies all Git actions on the protocol
|
||||
not selected.
|
||||
GitLab only allows Git actions for the protocols you select.
|
||||
|
||||
WARNING:
|
||||
GitLab versions [10.7 and later](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18021),
|
||||
allow the HTTP(S) protocol for Git clone or fetch requests done by GitLab Runner
|
||||
from CI/CD jobs, even if **Only SSH** was selected.
|
||||
from CI/CD jobs, even if you select **Only SSH**.
|
||||
|
||||
## Custom Git clone URL for HTTP(S)
|
||||
## Customize Git clone URL for HTTP(S)
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18422) in GitLab 12.4.
|
||||
|
||||
You can customize project Git clone URLs for HTTP(S), which affects the clone
|
||||
panel:
|
||||
|
||||
![Clone panel](img/clone_panel_v12_4.png)
|
||||
|
||||
For example, if:
|
||||
|
||||
- Your GitLab instance is at `https://example.com`, then project clone URLs are like
|
||||
|
@ -231,7 +281,7 @@ NOTE:
|
|||
SSH clone URLs can be customized in `gitlab.rb` by setting `gitlab_rails['gitlab_ssh_host']` and
|
||||
other related settings.
|
||||
|
||||
## RSA, DSA, ECDSA, ED25519 SSH keys
|
||||
## Configure defaults for RSA, DSA, ECDSA, ED25519 SSH keys
|
||||
|
||||
These options specify the permitted types and lengths for SSH keys.
|
||||
|
||||
|
@ -242,7 +292,7 @@ To specify a restriction for each key type:
|
|||
|
||||
For more details, see [SSH key restrictions](../../../security/ssh_keys_restrictions.md).
|
||||
|
||||
## Allow mirrors to be set up for projects
|
||||
## Enable project mirroring
|
||||
|
||||
This option is enabled by default. By disabling it, both
|
||||
[pull and push mirroring](../../project/repository/repository_mirroring.md) no longer
|
||||
|
|
|
@ -37,7 +37,7 @@ Like projects, a group can be configured to limit the visibility of it to:
|
|||
- All signed-in users.
|
||||
- Only explicit group members.
|
||||
|
||||
The restriction for [visibility levels](../admin_area/settings/visibility_and_access_controls.md#restricted-visibility-levels)
|
||||
The restriction for [visibility levels](../admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels)
|
||||
on the application setting level also applies to groups. If set to internal, the explore page is
|
||||
empty for anonymous users. The group page has a visibility level icon.
|
||||
|
||||
|
@ -220,10 +220,10 @@ To change this setting for a specific group:
|
|||
1. Select the desired option in the **Default branch protection** dropdown list.
|
||||
1. Click **Save changes**.
|
||||
|
||||
To change this setting globally, see [Default branch protection](../admin_area/settings/visibility_and_access_controls.md#default-branch-protection).
|
||||
To change this setting globally, see [Default branch protection](../admin_area/settings/visibility_and_access_controls.md#protect-default-branches).
|
||||
|
||||
NOTE:
|
||||
In [GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can choose to [disable group owners from updating the default branch protection](../admin_area/settings/visibility_and_access_controls.md#disable-group-owners-from-updating-default-branch-protection).
|
||||
In [GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can choose to [disable group owners from updating the default branch protection](../admin_area/settings/visibility_and_access_controls.md#prevent-overrides-of-default-branch-protection).
|
||||
|
||||
## Add projects to a group
|
||||
|
||||
|
@ -248,7 +248,7 @@ To change this setting for a specific group:
|
|||
1. Select the desired option in the **Allowed to create projects** dropdown list.
|
||||
1. Click **Save changes**.
|
||||
|
||||
To change this setting globally, see [Default project creation protection](../admin_area/settings/visibility_and_access_controls.md#default-project-creation-protection).
|
||||
To change this setting globally, see [Default project creation protection](../admin_area/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects).
|
||||
|
||||
## Group activity analytics **(PREMIUM)**
|
||||
|
||||
|
|
|
@ -330,7 +330,7 @@ The following table lists group permissions available for each role:
|
|||
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
|
||||
1. Introduced in GitLab 12.2.
|
||||
1. Default project creation role can be changed at:
|
||||
- The [instance level](admin_area/settings/visibility_and_access_controls.md#default-project-creation-protection).
|
||||
- The [instance level](admin_area/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects).
|
||||
- The [group level](group/index.md#specify-who-can-add-projects-to-a-group).
|
||||
1. Does not apply to subgroups.
|
||||
1. Developers can push commits to the default branch of a new project only if the [default branch protection](group/index.md#change-the-default-branch-protection-of-a-group) is set to "Partially protected" or "Not protected".
|
||||
|
|
|
@ -86,7 +86,7 @@ not.
|
|||
|
||||
When visiting the public page of a user, you can only see the projects which you have privileges to.
|
||||
|
||||
If the [public level is restricted](../admin_area/settings/visibility_and_access_controls.md#restricted-visibility-levels),
|
||||
If the [public level is restricted](../admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels),
|
||||
user profiles are only visible to signed-in users.
|
||||
|
||||
## Add external accounts to your user profile page
|
||||
|
|
|
@ -36,4 +36,4 @@ of the project being imported into, then the user will be linked.
|
|||
|
||||
## Enable this feature
|
||||
|
||||
Enable Phabricator as an [import source](../../admin_area/settings/visibility_and_access_controls.md#import-sources) in the Admin Area.
|
||||
Enable Phabricator as an [import source](../../admin_area/settings/visibility_and_access_controls.md#configure-allowed-import-sources) in the Admin Area.
|
||||
|
|
|
@ -30,7 +30,7 @@ When a branch is protected, the default behavior enforces these restrictions on
|
|||
### Set the default branch protection level
|
||||
|
||||
Administrators can set a default branch protection level in the
|
||||
[Admin Area](../admin_area/settings/visibility_and_access_controls.md#default-branch-protection).
|
||||
[Admin Area](../admin_area/settings/visibility_and_access_controls.md#protect-default-branches).
|
||||
|
||||
## Configure a protected branch
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ projects with the largest number of comments in the past month, click **Trending
|
|||
|
||||
NOTE:
|
||||
By default, `/explore` is visible to unauthenticated users. However, if the
|
||||
[**Public** visibility level](../admin_area/settings/visibility_and_access_controls.md#restricted-visibility-levels)
|
||||
[**Public** visibility level](../admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels)
|
||||
is restricted, `/explore` is visible only to signed-in users.
|
||||
|
||||
## Create a project
|
||||
|
|
|
@ -341,9 +341,9 @@ module Gitlab
|
|||
# - Per connection (requires a cleanup after the execution)
|
||||
#
|
||||
# When using a per connection disable statement, code must be inside
|
||||
# a block so we can automatically execute `RESET ALL` after block finishes
|
||||
# a block so we can automatically execute `RESET statement_timeout` after block finishes
|
||||
# otherwise the statement will still be disabled until connection is dropped
|
||||
# or `RESET ALL` is executed
|
||||
# or `RESET statement_timeout` is executed
|
||||
def disable_statement_timeout
|
||||
if block_given?
|
||||
if statement_timeout_disabled?
|
||||
|
@ -357,7 +357,7 @@ module Gitlab
|
|||
|
||||
yield
|
||||
ensure
|
||||
execute('RESET ALL')
|
||||
execute('RESET statement_timeout')
|
||||
end
|
||||
end
|
||||
else
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
module Gitlab
|
||||
module Database
|
||||
module Partitioning
|
||||
class DetachedPartitionDropper
|
||||
def perform
|
||||
return unless Feature.enabled?(:drop_detached_partitions, default_enabled: :yaml)
|
||||
|
||||
Gitlab::AppLogger.info(message: "Checking for previously detached partitions to drop")
|
||||
Postgresql::DetachedPartition.ready_to_drop.find_each do |detached_partition|
|
||||
conn.transaction do
|
||||
# Another process may have already dropped the table and deleted this entry
|
||||
next unless (detached_partition = Postgresql::DetachedPartition.lock.find_by(id: detached_partition.id))
|
||||
|
||||
unless check_partition_detached?(detached_partition)
|
||||
Gitlab::AppLogger.error(message: "Attempt to drop attached database partition", partition_name: detached_partition.table_name)
|
||||
detached_partition.destroy!
|
||||
next
|
||||
end
|
||||
|
||||
drop_one(detached_partition)
|
||||
end
|
||||
rescue StandardError => e
|
||||
Gitlab::AppLogger.error(message: "Failed to drop previously detached partition",
|
||||
partition_name: detached_partition.table_name,
|
||||
exception_class: e.class,
|
||||
exception_message: e.message)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def drop_one(detached_partition)
|
||||
conn.transaction do
|
||||
conn.execute(<<~SQL)
|
||||
DROP TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{conn.quote_table_name(detached_partition.table_name)}
|
||||
SQL
|
||||
|
||||
detached_partition.destroy!
|
||||
end
|
||||
Gitlab::AppLogger.info(message: "Dropped previously detached partition", partition_name: detached_partition.table_name)
|
||||
end
|
||||
|
||||
def check_partition_detached?(detached_partition)
|
||||
# PostgresPartition checks the pg_inherits view, so our partition will only show here if it's still attached
|
||||
# and thus should not be dropped
|
||||
!PostgresPartition.for_identifier("#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{detached_partition.table_name}").exists?
|
||||
end
|
||||
|
||||
def conn
|
||||
@conn ||= ApplicationRecord.connection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -86,7 +86,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def pruning_old_partitions?
|
||||
Feature.enabled?(:partition_pruning_dry_run) && retain_for.present?
|
||||
retain_for.present?
|
||||
end
|
||||
|
||||
def oldest_active_date
|
||||
|
|
|
@ -4,6 +4,8 @@ module Gitlab
|
|||
module Database
|
||||
module Partitioning
|
||||
class PartitionManager
|
||||
UnsafeToDetachPartitionError = Class.new(StandardError)
|
||||
|
||||
def self.register(model)
|
||||
raise ArgumentError, "Only models with a #partitioning_strategy can be registered." unless model.respond_to?(:partitioning_strategy)
|
||||
|
||||
|
@ -16,6 +18,7 @@ module Gitlab
|
|||
|
||||
LEASE_TIMEOUT = 1.minute
|
||||
MANAGEMENT_LEASE_KEY = 'database_partition_management_%s'
|
||||
RETAIN_DETACHED_PARTITIONS_FOR = 1.week
|
||||
|
||||
attr_reader :models
|
||||
|
||||
|
@ -35,13 +38,16 @@ module Gitlab
|
|||
partitions_to_create = missing_partitions(model)
|
||||
create(partitions_to_create) unless partitions_to_create.empty?
|
||||
|
||||
if Feature.enabled?(:partition_pruning_dry_run)
|
||||
if Feature.enabled?(:partition_pruning, default_enabled: :yaml)
|
||||
partitions_to_detach = extra_partitions(model)
|
||||
detach(partitions_to_detach) unless partitions_to_detach.empty?
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
Gitlab::AppLogger.error("Failed to create / detach partition(s) for #{model.table_name}: #{e.class}: #{e.message}")
|
||||
Gitlab::AppLogger.error(message: "Failed to create / detach partition(s)",
|
||||
table_name: model.table_name,
|
||||
exception_class: e.class,
|
||||
exception_message: e.message)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -54,7 +60,6 @@ module Gitlab
|
|||
end
|
||||
|
||||
def extra_partitions(model)
|
||||
return [] unless Feature.enabled?(:partition_pruning_dry_run)
|
||||
return [] unless connection.table_exists?(model.table_name)
|
||||
|
||||
model.partitioning_strategy.extra_partitions
|
||||
|
@ -74,7 +79,9 @@ module Gitlab
|
|||
partitions.each do |partition|
|
||||
connection.execute partition.to_sql
|
||||
|
||||
Gitlab::AppLogger.info("Created partition #{partition.partition_name} for table #{partition.table}")
|
||||
Gitlab::AppLogger.info(message: "Created partition",
|
||||
partition_name: partition.partition_name,
|
||||
table_name: partition.table)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -89,7 +96,24 @@ module Gitlab
|
|||
end
|
||||
|
||||
def detach_one_partition(partition)
|
||||
Gitlab::AppLogger.info("Planning to detach #{partition.partition_name} for table #{partition.table}")
|
||||
assert_partition_detachable!(partition)
|
||||
|
||||
connection.execute partition.to_detach_sql
|
||||
|
||||
Postgresql::DetachedPartition.create!(table_name: partition.partition_name,
|
||||
drop_after: RETAIN_DETACHED_PARTITIONS_FOR.from_now)
|
||||
|
||||
Gitlab::AppLogger.info(message: "Detached Partition",
|
||||
partition_name: partition.partition_name,
|
||||
table_name: partition.table)
|
||||
end
|
||||
|
||||
def assert_partition_detachable!(partition)
|
||||
parent_table_identifier = "#{connection.current_schema}.#{partition.table}"
|
||||
|
||||
if (example_fk = PostgresForeignKey.by_referenced_table_identifier(parent_table_identifier).first)
|
||||
raise UnsafeToDetachPartitionError, "Cannot detach #{partition.partition_name}, it would block while checking foreign key #{example_fk.name} on #{example_fk.constrained_table_identifier}"
|
||||
end
|
||||
end
|
||||
|
||||
def with_lock_retries(&block)
|
||||
|
|
|
@ -16,6 +16,7 @@ module Gitlab
|
|||
|
||||
gauge_present.set({ table: model.table_name }, strategy.current_partitions.size)
|
||||
gauge_missing.set({ table: model.table_name }, strategy.missing_partitions.size)
|
||||
gauge_extra.set({ table: model.table_name }, strategy.extra_partitions.size)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -28,6 +29,10 @@ module Gitlab
|
|||
def gauge_missing
|
||||
@gauge_missing ||= Gitlab::Metrics.gauge(:db_partitions_missing, 'Number of database partitions currently expected, but not present')
|
||||
end
|
||||
|
||||
def gauge_extra
|
||||
@gauge_extra ||= Gitlab::Metrics.gauge(:db_partitions_extra, 'Number of database partitions currently attached to tables, but outside of their retention window and scheduled to be dropped')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -47,6 +47,13 @@ module Gitlab
|
|||
SQL
|
||||
end
|
||||
|
||||
def to_detach_sql
|
||||
<<~SQL
|
||||
ALTER TABLE #{conn.quote_table_name(table)}
|
||||
DETACH PARTITION #{fully_qualified_partition}
|
||||
SQL
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
table == other.table && partition_name == other.partition_name && from == other.from && to == other.to
|
||||
end
|
||||
|
|
|
@ -7,10 +7,14 @@ module Gitlab
|
|||
|
||||
belongs_to :postgres_partitioned_table, foreign_key: 'parent_identifier', primary_key: 'identifier'
|
||||
|
||||
scope :by_identifier, ->(identifier) do
|
||||
scope :for_identifier, ->(identifier) do
|
||||
raise ArgumentError, "Partition name is not fully qualified with a schema: #{identifier}" unless identifier =~ /^\w+\.\w+$/
|
||||
|
||||
find(identifier)
|
||||
where(primary_key => identifier)
|
||||
end
|
||||
|
||||
scope :by_identifier, ->(identifier) do
|
||||
for_identifier(identifier).first!
|
||||
end
|
||||
|
||||
scope :for_parent_table, ->(name) { where("parent_identifier = concat(current_schema(), '.', ?)", name).order(:name) }
|
||||
|
|
|
@ -170,7 +170,6 @@
|
|||
category: testing
|
||||
redis_slot: testing
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_i_testing_full_code_quality_report_total
|
||||
- name: i_testing_web_performance_widget_total
|
||||
category: testing
|
||||
redis_slot: testing
|
||||
|
|
9
spec/factories/customer_relations/organizations.rb
Normal file
9
spec/factories/customer_relations/organizations.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :organization, class: 'CustomerRelations::Organization' do
|
||||
group
|
||||
|
||||
name { generate(:name) }
|
||||
end
|
||||
end
|
|
@ -414,9 +414,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
expect(model).to receive(:execute).with(/REFERENCES users \(id\)/)
|
||||
|
||||
|
@ -428,9 +428,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
expect(model).to receive(:execute).with(/REFERENCES users \(id_convert_to_bigint\)/)
|
||||
|
||||
|
@ -446,9 +446,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
|
||||
|
||||
|
@ -463,9 +463,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
expect(model).to receive(:execute).with(/ON DELETE CASCADE/)
|
||||
|
||||
|
@ -480,9 +480,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
expect(model).not_to receive(:execute).with(/ON DELETE/)
|
||||
|
||||
|
@ -498,10 +498,10 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
|
||||
end
|
||||
|
@ -527,10 +527,10 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
|
||||
end
|
||||
|
@ -557,10 +557,10 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+bar/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :bar)
|
||||
end
|
||||
|
@ -592,9 +592,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
expect(model).to receive(:execute).with('LOCK TABLE users, projects IN SHARE ROW EXCLUSIVE MODE')
|
||||
expect(model).to receive(:execute).with(/REFERENCES users \(id\)/)
|
||||
|
@ -614,9 +614,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).not_to receive(:concurrent_foreign_key_name)
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/ALTER TABLE projects VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
end
|
||||
|
||||
model.validate_foreign_key(:projects, :user_id, name: :foo)
|
||||
|
@ -631,9 +631,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:concurrent_foreign_key_name)
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/ALTER TABLE projects VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
end
|
||||
|
||||
model.validate_foreign_key(:projects, :user_id)
|
||||
|
@ -748,7 +748,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
end
|
||||
|
||||
after do
|
||||
model.execute('RESET ALL')
|
||||
model.execute('RESET statement_timeout')
|
||||
end
|
||||
|
||||
it 'defines statement to 0 only for current transaction' do
|
||||
|
@ -765,7 +765,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
context 'when passing a blocks' do
|
||||
it 'disables statement timeouts on session level and executes the block' do
|
||||
expect(model).to receive(:execute).with('SET statement_timeout TO 0')
|
||||
expect(model).to receive(:execute).with('RESET ALL').at_least(:once)
|
||||
expect(model).to receive(:execute).with('RESET statement_timeout').at_least(:once)
|
||||
|
||||
expect { |block| model.disable_statement_timeout(&block) }.to yield_control
|
||||
end
|
||||
|
@ -777,7 +777,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
end
|
||||
|
||||
after do
|
||||
model.execute('RESET ALL')
|
||||
model.execute('RESET statement_timeout')
|
||||
end
|
||||
|
||||
it 'defines statement to 0 for any code run inside the block' do
|
||||
|
@ -804,12 +804,12 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
after do
|
||||
# Use ActiveRecord::Base.connection instead of model.execute
|
||||
# so that this call is not counted below
|
||||
ActiveRecord::Base.connection.execute('RESET ALL')
|
||||
ActiveRecord::Base.connection.execute('RESET statement_timeout')
|
||||
end
|
||||
|
||||
it 'yields control without disabling the timeout or resetting' do
|
||||
expect(model).not_to receive(:execute).with('SET statement_timeout TO 0')
|
||||
expect(model).not_to receive(:execute).with('RESET ALL')
|
||||
expect(model).not_to receive(:execute).with('RESET statement_timeout')
|
||||
|
||||
expect { |block| model.disable_statement_timeout(&block) }.to yield_control
|
||||
end
|
||||
|
@ -2532,7 +2532,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:execute).with(/ADD CONSTRAINT check_name_not_null/)
|
||||
|
||||
|
@ -2542,7 +2542,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
.and_return(true).exactly(1)
|
||||
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
model.add_check_constraint(
|
||||
:test_table,
|
||||
|
@ -2576,7 +2576,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:with_lock_retries).and_call_original
|
||||
expect(model).to receive(:execute).with(/ADD CONSTRAINT check_name_not_null/)
|
||||
|
||||
|
@ -2585,7 +2585,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
.and_return(true).exactly(1)
|
||||
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
model.add_check_constraint(
|
||||
:test_table,
|
||||
|
@ -2618,9 +2618,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
expect(model).to receive(:check_constraint_exists?).and_return(true)
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(validate_sql)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
model.validate_check_constraint(:test_table, 'check_name')
|
||||
end
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do
|
||||
include Database::TableSchemaHelpers
|
||||
|
||||
let(:connection) { ActiveRecord::Base.connection }
|
||||
|
||||
def expect_partition_present(name)
|
||||
aggregate_failures do
|
||||
expect(table_oid(name)).not_to be_nil
|
||||
expect(Postgresql::DetachedPartition.find_by(table_name: name)).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
def expect_partition_removed(name)
|
||||
aggregate_failures do
|
||||
expect(table_oid(name)).to be_nil
|
||||
expect(Postgresql::DetachedPartition.find_by(table_name: name)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
connection.execute(<<~SQL)
|
||||
CREATE TABLE parent_table (
|
||||
id bigserial not null,
|
||||
created_at timestamptz not null,
|
||||
primary key (id, created_at)
|
||||
) PARTITION BY RANGE(created_at)
|
||||
SQL
|
||||
end
|
||||
|
||||
def create_partition(name:, table: 'parent_table', from:, to:, attached:, drop_after:)
|
||||
from = from.beginning_of_month
|
||||
to = to.beginning_of_month
|
||||
full_name = "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{name}"
|
||||
connection.execute(<<~SQL)
|
||||
CREATE TABLE #{full_name}
|
||||
PARTITION OF #{table}
|
||||
FOR VALUES FROM ('#{from.strftime('%Y-%m-%d')}') TO ('#{to.strftime('%Y-%m-%d')}')
|
||||
SQL
|
||||
|
||||
unless attached
|
||||
connection.execute(<<~SQL)
|
||||
ALTER TABLE #{table} DETACH PARTITION #{full_name}
|
||||
SQL
|
||||
end
|
||||
|
||||
Postgresql::DetachedPartition.create!(table_name: name,
|
||||
drop_after: drop_after)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when the partition should not be dropped yet' do
|
||||
it 'does not drop the partition' do
|
||||
create_partition(name: 'test_partition',
|
||||
from: 2.months.ago, to: 1.month.ago,
|
||||
attached: false,
|
||||
drop_after: 1.day.from_now)
|
||||
|
||||
subject.perform
|
||||
|
||||
expect_partition_present('test_partition')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a partition to drop' do
|
||||
before do
|
||||
create_partition(name: 'test_partition',
|
||||
from: 2.months.ago,
|
||||
to: 1.month.ago.beginning_of_month,
|
||||
attached: false,
|
||||
drop_after: 1.second.ago)
|
||||
end
|
||||
|
||||
it 'drops the partition' do
|
||||
subject.perform
|
||||
|
||||
expect(table_oid('test_partition')).to be_nil
|
||||
end
|
||||
|
||||
context 'when the drop_detached_partitions feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(drop_detached_partitions: false)
|
||||
end
|
||||
it 'does not drop the partition' do
|
||||
subject.perform
|
||||
|
||||
expect(table_oid('test_partition')).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when another process drops the table while the first waits for a lock' do
|
||||
it 'skips the table' do
|
||||
# Rspec's receive_method_chain does not support .and_wrap_original, so we need to nest here.
|
||||
expect(Postgresql::DetachedPartition).to receive(:lock).and_wrap_original do |lock_meth|
|
||||
locked = lock_meth.call
|
||||
expect(locked).to receive(:find_by).and_wrap_original do |find_meth, *find_args|
|
||||
# Another process drops the table then deletes this entry
|
||||
Postgresql::DetachedPartition.where(*find_args).delete_all
|
||||
find_meth.call(*find_args)
|
||||
end
|
||||
|
||||
locked
|
||||
end
|
||||
|
||||
expect(subject).not_to receive(:drop_one)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the partition to drop is still attached to its table' do
|
||||
before do
|
||||
create_partition(name: 'test_partition',
|
||||
from: 2.months.ago,
|
||||
to: 1.month.ago.beginning_of_month,
|
||||
attached: true,
|
||||
drop_after: 1.second.ago)
|
||||
end
|
||||
|
||||
it 'does not drop the partition, but does remove the DetachedPartition entry' do
|
||||
subject.perform
|
||||
aggregate_failures do
|
||||
expect(table_oid('test_partition')).not_to be_nil
|
||||
expect(Postgresql::DetachedPartition.find_by(table_name: 'test_partition')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'removes the detached_partition entry' do
|
||||
detached_partition = Postgresql::DetachedPartition.find_by!(table_name: 'test_partition')
|
||||
|
||||
subject.perform
|
||||
|
||||
expect(Postgresql::DetachedPartition.exists?(id: detached_partition.id)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple partitions to drop' do
|
||||
before do
|
||||
create_partition(name: 'partition_1',
|
||||
from: 3.months.ago,
|
||||
to: 2.months.ago,
|
||||
attached: false,
|
||||
drop_after: 1.second.ago)
|
||||
|
||||
create_partition(name: 'partition_2',
|
||||
from: 2.months.ago,
|
||||
to: 1.month.ago,
|
||||
attached: false,
|
||||
drop_after: 1.second.ago)
|
||||
end
|
||||
|
||||
it 'drops both partitions' do
|
||||
subject.perform
|
||||
|
||||
expect_partition_removed('partition_1')
|
||||
expect_partition_removed('partition_2')
|
||||
end
|
||||
|
||||
context 'when the first drop returns an error' do
|
||||
it 'still drops the second partition' do
|
||||
expect(subject).to receive(:drop_one).ordered.and_raise('injected error')
|
||||
expect(subject).to receive(:drop_one).ordered.and_call_original
|
||||
|
||||
subject.perform
|
||||
|
||||
# We don't know which partition we tried to drop first, so the tests here have to work with either one
|
||||
expect(Postgresql::DetachedPartition.count).to eq(1)
|
||||
errored_partition_name = Postgresql::DetachedPartition.first!.table_name
|
||||
|
||||
dropped_partition_name = (%w[partition_1 partition_2] - [errored_partition_name]).first
|
||||
expect_partition_present(errored_partition_name)
|
||||
expect_partition_removed(dropped_partition_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -237,16 +237,6 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy do
|
|||
|
||||
expect(subject).to contain_exactly(min_value_to_may)
|
||||
end
|
||||
|
||||
context 'when the feature flag is toggled off' do
|
||||
before do
|
||||
stub_feature_flags(partition_pruning_dry_run: false)
|
||||
end
|
||||
|
||||
it 'is empty' do
|
||||
expect(subject).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a time retention policy of 2 months' do
|
||||
|
@ -258,16 +248,6 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy do
|
|||
Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01', partition_name: 'partitioned_test_202005')
|
||||
)
|
||||
end
|
||||
|
||||
context 'when the feature flag is toggled off' do
|
||||
before do
|
||||
stub_feature_flags(partition_pruning_dry_run: false)
|
||||
end
|
||||
|
||||
it 'is empty' do
|
||||
expect(subject).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,9 +4,14 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
||||
include Database::PartitioningHelpers
|
||||
include Database::TableSchemaHelpers
|
||||
include ExclusiveLeaseHelpers
|
||||
|
||||
def has_partition(model, month)
|
||||
Gitlab::Database::PostgresPartition.for_parent_table(model.table_name).any? do |partition|
|
||||
Gitlab::Database::Partitioning::TimePartition.from_sql(model.table_name, partition.name, partition.condition).from == month
|
||||
end
|
||||
end
|
||||
|
||||
describe '.register' do
|
||||
let(:model) { double(partitioning_strategy: nil) }
|
||||
|
||||
|
@ -111,14 +116,14 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
|||
|
||||
let(:extra_partitions) do
|
||||
[
|
||||
instance_double(Gitlab::Database::Partitioning::TimePartition, table: table, partition_name: 'foo1'),
|
||||
instance_double(Gitlab::Database::Partitioning::TimePartition, table: table, partition_name: 'foo2')
|
||||
instance_double(Gitlab::Database::Partitioning::TimePartition, table: table, partition_name: 'foo1', to_detach_sql: 'SELECT 1'),
|
||||
instance_double(Gitlab::Database::Partitioning::TimePartition, table: table, partition_name: 'foo2', to_detach_sql: 'SELECT 2')
|
||||
]
|
||||
end
|
||||
|
||||
context 'with the partition_pruning_dry_run feature flag enabled' do
|
||||
context 'with the partition_pruning feature flag enabled' do
|
||||
before do
|
||||
stub_feature_flags(partition_pruning_dry_run: true)
|
||||
stub_feature_flags(partition_pruning: true)
|
||||
end
|
||||
|
||||
it 'detaches each extra partition' do
|
||||
|
@ -146,9 +151,9 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with the partition_pruning_dry_run feature flag disabled' do
|
||||
context 'with the partition_pruning feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(partition_pruning_dry_run: false)
|
||||
stub_feature_flags(partition_pruning: false)
|
||||
end
|
||||
|
||||
it 'returns immediately' do
|
||||
|
@ -158,4 +163,128 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#detach_partitions' do
|
||||
around do |ex|
|
||||
travel_to(Date.parse('2021-06-23')) do
|
||||
ex.run
|
||||
end
|
||||
end
|
||||
|
||||
subject { described_class.new([my_model]).sync_partitions }
|
||||
|
||||
let(:connection) { ActiveRecord::Base.connection }
|
||||
let(:my_model) do
|
||||
Class.new(ApplicationRecord) do
|
||||
include PartitionedTable
|
||||
|
||||
self.table_name = 'my_model_example_table'
|
||||
|
||||
partitioned_by :created_at, strategy: :monthly, retain_for: 1.month
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
connection.execute(<<~SQL)
|
||||
CREATE TABLE my_model_example_table
|
||||
(id serial not null, created_at timestamptz not null, primary key (id, created_at))
|
||||
PARTITION BY RANGE (created_at);
|
||||
|
||||
CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.my_model_example_table_202104
|
||||
PARTITION OF my_model_example_table
|
||||
FOR VALUES FROM ('2021-04-01') TO ('2021-05-01');
|
||||
|
||||
CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.my_model_example_table_202105
|
||||
PARTITION OF my_model_example_table
|
||||
FOR VALUES FROM ('2021-05-01') TO ('2021-06-01');
|
||||
SQL
|
||||
|
||||
# Also create all future partitions so that the sync is only trying to detach old partitions
|
||||
my_model.partitioning_strategy.missing_partitions.each do |p|
|
||||
connection.execute p.to_sql
|
||||
end
|
||||
end
|
||||
|
||||
def num_tables
|
||||
connection.select_value(<<~SQL)
|
||||
SELECT COUNT(*)
|
||||
FROM pg_class
|
||||
where relkind IN ('r', 'p')
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'detaches exactly one partition' do
|
||||
expect { subject }.to change { find_partitions(my_model.table_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA).size }.from(9).to(8)
|
||||
end
|
||||
|
||||
it 'detaches the old partition' do
|
||||
expect { subject }.to change { has_partition(my_model, 2.months.ago.beginning_of_month) }.from(true).to(false)
|
||||
end
|
||||
|
||||
it 'deletes zero tables' do
|
||||
expect { subject }.not_to change { num_tables }
|
||||
end
|
||||
|
||||
it 'creates the appropriate PendingPartitionDrop entry' do
|
||||
subject
|
||||
|
||||
pending_drop = Postgresql::DetachedPartition.find_by!(table_name: 'my_model_example_table_202104')
|
||||
expect(pending_drop.drop_after).to eq(Time.current + described_class::RETAIN_DETACHED_PARTITIONS_FOR)
|
||||
end
|
||||
|
||||
# Postgres 11 does not support foreign keys to partitioned tables
|
||||
if Gitlab::Database.main.version.to_f >= 12
|
||||
context 'when the model is the target of a foreign key' do
|
||||
before do
|
||||
connection.execute(<<~SQL)
|
||||
create unique index idx_for_fk ON my_model_example_table(created_at);
|
||||
|
||||
create table referencing_table (
|
||||
id bigserial primary key not null,
|
||||
referencing_created_at timestamptz references my_model_example_table(created_at)
|
||||
);
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'does not detach partitions with a referenced foreign key' do
|
||||
expect { subject }.not_to change { find_partitions(my_model.table_name).size }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'creating and then detaching partitions for a table' do
|
||||
let(:connection) { ActiveRecord::Base.connection }
|
||||
let(:my_model) do
|
||||
Class.new(ApplicationRecord) do
|
||||
include PartitionedTable
|
||||
|
||||
self.table_name = 'my_model_example_table'
|
||||
|
||||
partitioned_by :created_at, strategy: :monthly, retain_for: 1.month
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
connection.execute(<<~SQL)
|
||||
CREATE TABLE my_model_example_table
|
||||
(id serial not null, created_at timestamptz not null, primary key (id, created_at))
|
||||
PARTITION BY RANGE (created_at);
|
||||
SQL
|
||||
end
|
||||
|
||||
def num_partitions(model)
|
||||
find_partitions(model.table_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA).size
|
||||
end
|
||||
|
||||
it 'creates partitions for the future then drops the oldest one after a month' do
|
||||
# 1 month for the current month, 1 month for the old month that we're retaining data for, headroom
|
||||
expected_num_partitions = (Gitlab::Database::Partitioning::MonthlyStrategy::HEADROOM + 2.months) / 1.month
|
||||
expect { described_class.new([my_model]).sync_partitions }.to change { num_partitions(my_model) }.from(0).to(expected_num_partitions)
|
||||
|
||||
travel 1.month
|
||||
|
||||
expect { described_class.new([my_model]).sync_partitions }.to change { has_partition(my_model, 2.months.ago.beginning_of_month) }.from(true).to(false).and(change { num_partitions(my_model) }.by(0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionMonitoring do
|
|||
|
||||
let(:models) { [model] }
|
||||
let(:model) { double(partitioning_strategy: partitioning_strategy, table_name: table) }
|
||||
let(:partitioning_strategy) { double(missing_partitions: missing_partitions, current_partitions: current_partitions) }
|
||||
let(:partitioning_strategy) { double(missing_partitions: missing_partitions, current_partitions: current_partitions, extra_partitions: extra_partitions) }
|
||||
let(:table) { "some_table" }
|
||||
|
||||
let(:missing_partitions) do
|
||||
|
@ -19,6 +19,10 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionMonitoring do
|
|||
[double, double]
|
||||
end
|
||||
|
||||
let(:extra_partitions) do
|
||||
[double, double, double]
|
||||
end
|
||||
|
||||
it 'reports number of present partitions' do
|
||||
subject
|
||||
|
||||
|
@ -30,5 +34,11 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionMonitoring do
|
|||
|
||||
expect(Gitlab::Metrics.registry.get(:db_partitions_missing).get({ table: table })).to eq(missing_partitions.size)
|
||||
end
|
||||
|
||||
it 'reports number of extra partitions' do
|
||||
subject
|
||||
|
||||
expect(Gitlab::Metrics.registry.get(:db_partitions_extra).get({ table: table })).to eq(extra_partitions.size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
38
spec/models/customer_relations/organization_spec.rb
Normal file
38
spec/models/customer_relations/organization_spec.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe CustomerRelations::Organization, type: :model do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:group).with_foreign_key('group_id') }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
subject { create(:organization) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:group) }
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_uniqueness_of(:name).case_insensitive.scoped_to([:group_id]) }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(255) }
|
||||
it { is_expected.to validate_length_of(:description).is_at_most(1024) }
|
||||
end
|
||||
|
||||
describe '#name' do
|
||||
it 'strips name' do
|
||||
organization = described_class.new(name: ' GitLab ')
|
||||
organization.valid?
|
||||
|
||||
expect(organization.name).to eq('GitLab')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_by_name' do
|
||||
let!(:group) { create(:group) }
|
||||
let!(:organiztion1) { create(:organization, group: group, name: 'Test') }
|
||||
let!(:organiztion2) { create(:organization, group: create(:group), name: 'Test') }
|
||||
|
||||
it 'strips name' do
|
||||
expect(described_class.find_by_name(group.id, 'TEST')).to eq([organiztion1])
|
||||
end
|
||||
end
|
||||
end
|
18
spec/models/postgresql/detached_partition_spec.rb
Normal file
18
spec/models/postgresql/detached_partition_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Postgresql::DetachedPartition do
|
||||
describe '#ready_to_drop' do
|
||||
let_it_be(:drop_before) { Postgresql::DetachedPartition.create!(drop_after: 1.day.ago, table_name: 'old_table') }
|
||||
let_it_be(:drop_after) { Postgresql::DetachedPartition.create!(drop_after: 1.day.from_now, table_name: 'new_table') }
|
||||
|
||||
it 'includes partitions that should be dropped before now' do
|
||||
expect(Postgresql::DetachedPartition.ready_to_drop.to_a).to include(drop_before)
|
||||
end
|
||||
|
||||
it 'does not include partitions that should be dropped after now' do
|
||||
expect(Postgresql::DetachedPartition.ready_to_drop.to_a).not_to include(drop_after)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,10 +14,10 @@ RSpec.shared_examples 'performs validation' do |validation_option|
|
|||
it 'performs validation' do
|
||||
expect(model).to receive(:disable_statement_timeout).and_call_original
|
||||
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
|
||||
expect(model).to receive(:execute).with(/statement_timeout/)
|
||||
expect(model).to receive(:execute).with(/SET statement_timeout TO/)
|
||||
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
|
||||
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
|
||||
expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
|
||||
|
||||
model.add_concurrent_foreign_key(*args, **options.merge(validation_option))
|
||||
end
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Database::DropDetachedPartitionsWorker do
|
||||
describe '#perform' do
|
||||
subject { described_class.new.perform }
|
||||
|
||||
let(:dropper) { instance_double('DropDetachedPartitions', perform: nil) }
|
||||
let(:monitoring) { instance_double('PartitionMonitoring', report_metrics: nil) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Database::Partitioning::DetachedPartitionDropper).to receive(:new).and_return(dropper)
|
||||
allow(Gitlab::Database::Partitioning::PartitionMonitoring).to receive(:new).and_return(monitoring)
|
||||
end
|
||||
|
||||
it 'delegates to DropPartitionsPendingDrop' do
|
||||
expect(dropper).to receive(:perform)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'reports partition metrics' do
|
||||
expect(monitoring).to receive(:report_metrics)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue