summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-20 00:08:57 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-20 00:08:57 +0000
commit049f94a652166ab0867fbd9d369e068dccfe6c35 (patch)
tree134f200659aa0ea5772fb1bc72cb3736114aa1b1
parent3d6aa9071097f5070c801bee13a619da0a297d07 (diff)
downloadgitlab-ce-049f94a652166ab0867fbd9d369e068dccfe6c35.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--db/migrate/20220516054002_temp_index_for_project_namespace_member_backfill.rb19
-rw-r--r--db/post_migrate/20220516054011_schedule_backfill_project_member_namespace_id.rb27
-rw-r--r--db/schema_migrations/202205160540021
-rw-r--r--db/schema_migrations/202205160540111
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/packages/container_registry.md34
-rw-r--r--doc/development/documentation/styleguide/word_list.md12
-rw-r--r--doc/integration/elasticsearch.md57
-rw-r--r--doc/user/group/index.md7
-rw-r--r--doc/user/packages/container_registry/index.md24
-rw-r--r--doc/user/packages/generic_packages/index.md5
-rw-r--r--lib/gitlab/background_migration/backfill_project_member_namespace_id.rb37
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb19
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_member_namespace_id_spec.rb104
-rw-r--r--spec/migrations/20220416054011_schedule_backfill_project_member_namespace_id_spec.rb29
15 files changed, 318 insertions, 60 deletions
diff --git a/db/migrate/20220516054002_temp_index_for_project_namespace_member_backfill.rb b/db/migrate/20220516054002_temp_index_for_project_namespace_member_backfill.rb
new file mode 100644
index 00000000000..a9dc14d7521
--- /dev/null
+++ b/db/migrate/20220516054002_temp_index_for_project_namespace_member_backfill.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class TempIndexForProjectNamespaceMemberBackfill < Gitlab::Database::Migration[1.0]
+ INDEX_NAME = 'tmp_index_for_namespace_id_migration_on_project_members'
+
+ disable_ddl_transaction!
+
+ def up
+ # Temporary index to be removed in future
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/356509
+ add_concurrent_index :members, :id,
+ where: "members.member_namespace_id IS NULL and members.type = 'ProjectMember'",
+ name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :members, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20220516054011_schedule_backfill_project_member_namespace_id.rb b/db/post_migrate/20220516054011_schedule_backfill_project_member_namespace_id.rb
new file mode 100644
index 00000000000..2d3537657be
--- /dev/null
+++ b/db/post_migrate/20220516054011_schedule_backfill_project_member_namespace_id.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class ScheduleBackfillProjectMemberNamespaceId < Gitlab::Database::Migration[1.0]
+ MIGRATION = 'BackfillProjectMemberNamespaceId'
+ INTERVAL = 2.minutes
+ BATCH_SIZE = 1_000
+ MAX_BATCH_SIZE = 2_000
+ SUB_BATCH_SIZE = 200
+
+ disable_ddl_transaction!
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :members,
+ :id,
+ job_interval: INTERVAL,
+ batch_size: BATCH_SIZE,
+ max_batch_size: MAX_BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, :members, :id, [])
+ end
+end
diff --git a/db/schema_migrations/20220516054002 b/db/schema_migrations/20220516054002
new file mode 100644
index 00000000000..3a9f63f2122
--- /dev/null
+++ b/db/schema_migrations/20220516054002
@@ -0,0 +1 @@
+72412b4e47f69737ecc50f234d182b2bb9c7d0a03426baffec137651613468c6 \ No newline at end of file
diff --git a/db/schema_migrations/20220516054011 b/db/schema_migrations/20220516054011
new file mode 100644
index 00000000000..0a1dff834ce
--- /dev/null
+++ b/db/schema_migrations/20220516054011
@@ -0,0 +1 @@
+23e7a5c3ea535b7faf0e9ba3e95d8ca1431ba96f5f431e0fed0e0c8df340d882 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index db235d6d2d6..430205adf08 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -29796,6 +29796,8 @@ CREATE INDEX tmp_index_container_repositories_on_id_migration_state ON container
CREATE INDEX tmp_index_for_namespace_id_migration_on_group_members ON members USING btree (id) WHERE ((member_namespace_id IS NULL) AND ((type)::text = 'GroupMember'::text));
+CREATE INDEX tmp_index_for_namespace_id_migration_on_project_members ON members USING btree (id) WHERE ((member_namespace_id IS NULL) AND ((type)::text = 'ProjectMember'::text));
+
CREATE INDEX tmp_index_for_namespace_id_migration_on_routes ON routes USING btree (id) WHERE ((namespace_id IS NULL) AND ((source_type)::text = 'Namespace'::text));
CREATE INDEX tmp_index_for_null_project_namespace_id ON projects USING btree (id) WHERE (project_namespace_id IS NULL);
diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md
index 0edad83cc18..3a465f4fccc 100644
--- a/doc/administration/packages/container_registry.md
+++ b/doc/administration/packages/container_registry.md
@@ -317,6 +317,17 @@ the Container Registry by themselves, follow the steps below.
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
+### Increase token duration
+
+In GitLab, tokens for the Container Registry expire every five minutes.
+To increase the token duration:
+
+1. On the top bar, select **Menu > Admin**.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **Container Registry**.
+1. For the **Authorization token duration (minutes)**, update the value.
+1. Select **Save changes**.
+
## Configure storage for the Container Registry
NOTE:
@@ -1226,29 +1237,6 @@ mounting the Docker daemon and setting `privileged = false` in the GitLab Runner
Additional information about this: [issue 18239](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/18239).
-### `unauthorized: authentication required` when pushing large images
-
-Example error:
-
-```shell
-docker push gitlab.example.com/myproject/docs:latest
-The push refers to a repository [gitlab.example.com/myproject/docs]
-630816f32edb: Preparing
-530d5553aec8: Preparing
-...
-4b0bab9ff599: Waiting
-d1c800db26c7: Waiting
-42755cf4ee95: Waiting
-unauthorized: authentication required
-```
-
-GitLab has a default token expiration of 5 minutes for the registry. When pushing
-larger images, or images that take longer than 5 minutes to push, users may
-encounter this error. On GitLab.com, the expiration time is 15 minutes.
-
-Administrators can increase the token duration in **Admin Area > Settings >
-CI/CD > Container Registry > Authorization token duration (minutes)**.
-
### Docker login attempt fails with: 'token signed by untrusted key'
[Registry relies on GitLab to validate credentials](#architecture-of-gitlab-container-registry)
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index e7d927de2cf..d094bb4c91a 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -139,6 +139,16 @@ Do not use **and so on**. Instead, be more specific. For details, see
Use [**section**](#section) instead of **area**. The only exception is [the Admin Area](#admin-area).
+## associate
+
+Do not use **associate** when describing adding issues to epics, or users to issues, merge requests,
+or epics.
+
+Instead, use **assign**. For example:
+
+- Assign the issue to an epic.
+- Assign a user to the issue.
+
## below
Try to avoid **below** when referring to an example or table in a documentation page. If required, use **following** instead. For example:
@@ -347,6 +357,8 @@ See also [**type**](#type).
Use lowercase for **epic**.
+See also [associate](#associate).
+
## epic board
Use lowercase for **epic board**.
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 214bd845944..433ec3a7948 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -7,36 +7,33 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Elasticsearch integration **(PREMIUM SELF)**
-> Moved to GitLab Premium in 13.9.
-
This page describes how to enable Advanced Search. When enabled,
Advanced Search provides faster search response times and [improved search features](../user/search/advanced_search.md).
## Version requirements
-> Support for Elasticsearch 6.8 was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/350275) in GitLab 14.8 and is scheduled for removal in GitLab 15.0.
+### Elasticsearch version requirements
+
+> Support for Elasticsearch 6.8 was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/350275) in GitLab 15.0.
+
+Advanced Search works with the following versions of Elasticsearch.
-| GitLab version | Elasticsearch version |
-|----------------------|--------------------------|
-| GitLab 14.8 or later | Elasticsearch 7.x - 7.17 |
-| GitLab 13.9 - 14.7 | Elasticsearch 6.8 - 7.x |
-| GitLab 13.3 - 13.8 | Elasticsearch 6.4 - 7.x |
-| GitLab 12.7 - 13.2 | Elasticsearch 6.x - 7.x |
-| GitLab 11.5 - 12.6 | Elasticsearch 5.6 - 6.x |
+| GitLab version | Elasticsearch version |
+|-----------------------|--------------------------|
+| GitLab 15.0 or later | Elasticsearch 7.x - 8.x |
+| GitLab 13.9 - 14.10 | Elasticsearch 6.8 - 7.x |
+| GitLab 13.3 - 13.8 | Elasticsearch 6.4 - 7.x |
+| GitLab 12.7 - 13.2 | Elasticsearch 6.x - 7.x |
-The Elasticsearch Integration works with supported versions of
-Elasticsearch and follows Elasticsearch's [End of Life Policy](https://www.elastic.co/support/eol).
+Advanced Search follows Elasticsearch's [End of Life Policy](https://www.elastic.co/support/eol).
When we change Elasticsearch supported versions in GitLab, we announce them in [deprecation notes](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations) in monthly release posts
before we remove them.
-### Versions not supported
+### OpenSearch version requirements
-GitLab does not support:
-
-- [Amazon's OpenSearch](https://aws.amazon.com/blogs/opensource/opensearch-1-0-launches/)
-(a [fork of Elasticsearch](https://www.elastic.co/what-is/opensearch)). Use AWS Elasticsearch Service 7.10 instead.
-For updates, see [issue #327560](https://gitlab.com/gitlab-org/gitlab/-/issues/327560).
-- Elasticsearch 8.0. For updates, see [issue #350600](https://gitlab.com/gitlab-org/gitlab/-/issues/350600). Use Elasticsearch 7.17 instead.
+| GitLab version | Elasticsearch version |
+|-----------------------|--------------------------|
+| GitLab 15.0 or later | OpenSearch 1.x or later |
## System requirements
@@ -59,8 +56,6 @@ source. You must [install it separately](https://www.elastic.co/guide/en/elastic
You can install Elasticsearch yourself, or use a cloud hosted offering such as [Elasticsearch Service](https://www.elastic.co/elasticsearch/service) (available on AWS, GCP, or Azure) or the [Amazon OpenSearch](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/gsg.html)
service.
-If using the Amazon OpenSearch service, ensure that you select `Elasticsearch 7.10` when configuring Deployment type. As noted in [Versions not supported](#versions-not-supported), Amazon's non-Elasticsearch versions are not yet supported.
-
You should install Elasticsearch on a separate server. Running Elasticsearch on the same server as GitLab is not recommended and can cause a degradation in GitLab instance performance.
For a single node Elasticsearch cluster, the functional cluster health status is always yellow due to the allocation of the primary shard. Elasticsearch cannot assign replica shards to the same node as primary shards.
@@ -72,14 +67,10 @@ The search index updates after you:
## Upgrade to a new Elasticsearch major version
-Elasticsearch reads and uses indices created in the previous major version. You are not required to change the GitLab configuration when you upgrade Elasticsearch.
-
-If your current index was created before GitLab 13.0, you must reindex from scratch to create an alias to use features such as [zero downtime reindexing](#zero-downtime-reindexing). After you reindex, you can perform zero downtime reindexing and also benefit from future features that use the alias.
+> - Elasticsearch 6.8 support is removed with GitLab 15.0.
+> - Upgrading from GitLab 14.10 to 15.0 requires that you are using any version of Elasticsearch 7.x.
-To check if your current index was created before GitLab 13.0, use the [Elasticsearch cat aliases API](https://www.elastic.co/guide/en/elasticsearch/reference/7.11/cat-alias.html).
-If the returned list of aliases does not contain a `gitlab-production` alias, you must reindex to use features such as zero downtime reindexing.
-If the returned list of aliases contains an entry for `gitlab-production` that points to an index
-named `gitlab-production-<numerical timestamp>`, your index was created after GitLab 13.0.
+You are not required to change the GitLab configuration when you upgrade Elasticsearch.
## Elasticsearch repository indexer
@@ -1101,3 +1092,13 @@ es:ESHttpPost
es:ESHttpPut
es:ESHttpPatch
```
+
+### Role-mapping when using AWS Elasticsearch or AWS OpenSearch fine-grained access control
+
+When using fine-grained access control with an IAM role, you might encounter the following error:
+
+```plaintext
+{"error":{"root_cause":[{"type":"security_exception","reason":"no permissions for [indices:data/write/bulk] and User [name=arn:aws:iam::xxx:role/INSERT_ROLE_NAME_HERE, backend_roles=[arn:aws:iam::xxx:role/INSERT_ROLE_NAME_HERE], requestedTenant=null]"}],"type":"security_exception","reason":"no permissions for [indices:data/write/bulk] and User [name=arn:aws:iam::xxx:role/INSERT_ROLE_NAME_HERE, backend_roles=[arn:aws:iam::xxx:role/INSERT_ROLE_NAME_HERE], requestedTenant=null]"},"status":403}
+```
+
+To fix this, you need to [map the roles to users](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html#fgac-mapping) in Kibana.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 87146329031..e3487ad73e0 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -619,9 +619,10 @@ You should consider these security implications before configuring IP address re
- **Administrators and group owners can access group settings from any IP address**: Users with these permission levels can always
access the group settings, regardless of IP restriction, but they cannot access projects
belonging to the group when accessing from a disallowed IP address.
-- **Some GitLab API endpoints will remain accessible from any IP**: Only the [group](../../api/groups.md) (including all
- [group resources](../../api/api_resources.md#group-resources)) APIs and [project](../../api/api_resources.md#project-resources)
- (including all [project resources](../../api/api_resources.md#project-resources)) APIs are protected by IP address restrictions.
+ - **Some GitLab API endpoints will remain accessible from any IP**: Users coming from denied IP addresses can still see group and project
+ names and hierarchies. Only the [group](../../api/groups.md) (including all [group resources](../../api/api_resources.md#group-resources))
+ APIs and [project](../../api/api_resources.md#project-resources) (including all [project resources](../../api/api_resources.md#project-resources))
+ APIs are protected by IP address restrictions.
- **Activities performed by GitLab Runners are not bound by IP restrictions**:
When you register a runner, it is not bound by the IP restrictions. When the runner
requests a new job or an update to a job's state, it is also not bound by
diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md
index fe7a41aa542..fffa21c308f 100644
--- a/doc/user/packages/container_registry/index.md
+++ b/doc/user/packages/container_registry/index.md
@@ -707,3 +707,27 @@ GitLab is [migrating to the next generation of the Container Registry](https://g
During the migration, you may encounter difficulty deleting tags.
If you encounter an error, it's likely that your image repository is in the process of being migrated.
Please wait a few minutes and try again.
+
+### `unauthorized: authentication required` when pushing large images
+
+When pushing large images, you might get an error like the following:
+
+```shell
+docker push gitlab.example.com/myproject/docs:latest
+The push refers to a repository [gitlab.example.com/myproject/docs]
+630816f32edb: Preparing
+530d5553aec8: Preparing
+...
+4b0bab9ff599: Waiting
+d1c800db26c7: Waiting
+42755cf4ee95: Waiting
+unauthorized: authentication required
+```
+
+On self-managed GitLab instances, by default, tokens for the Container Registry expire every five minutes.
+When pushing larger images, or images that take longer than five minutes to push,
+you might encounter this error. On GitLab.com, the expiration time is 15 minutes.
+
+If you are using self-managed GitLab, you can ask an administrator to
+[increase the token duration](../../../administration/packages/container_registry.md#increase-token-duration)
+if necessary.
diff --git a/doc/user/packages/generic_packages/index.md b/doc/user/packages/generic_packages/index.md
index 37e1f0c3eb1..a93d6919b0e 100644
--- a/doc/user/packages/generic_packages/index.md
+++ b/doc/user/packages/generic_packages/index.md
@@ -32,6 +32,11 @@ Prerequisites:
If authenticating with a deploy token, it must be configured with the `write_package_registry`
scope. If authenticating with a personal access token or project access token, it must be
configured with the `api` scope.
+- You must call this API endpoint serially when attempting to upload multiple files under the
+ same package name and version. Attempts to concurrently upload multiple files into
+ a new package name and version may face partial failures with
+ `HTTP 500: Internal Server Error` responses due to the requests racing to
+ create the package.
```plaintext
PUT /projects/:id/packages/generic/:package_name/:package_version/:file_name?status=:status
diff --git a/lib/gitlab/background_migration/backfill_project_member_namespace_id.rb b/lib/gitlab/background_migration/backfill_project_member_namespace_id.rb
new file mode 100644
index 00000000000..c2e37269b5e
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_project_member_namespace_id.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Backfills the `members.member_namespace_id` column for `type=ProjectMember`
+ class BackfillProjectMemberNamespaceId < Gitlab::BackgroundMigration::BatchedMigrationJob
+ def perform
+ parent_batch_relation = relation_scoped_to_range(batch_table, batch_column, start_id, end_id)
+
+ parent_batch_relation.each_batch(column: batch_column, of: sub_batch_size, order_hint: :type) do |sub_batch|
+ batch_metrics.time_operation(:update_all) do
+ # rubocop:disable Layout/LineLength
+ sub_batch.update_all('member_namespace_id = (SELECT projects.project_namespace_id FROM projects WHERE projects.id = source_id)')
+ # rubocop:enable Layout/LineLength
+ end
+
+ pause_ms_value = [0, pause_ms].max
+ sleep(pause_ms_value * 0.001)
+ end
+ end
+
+ def batch_metrics
+ @batch_metrics ||= Gitlab::Database::BackgroundMigration::BatchMetrics.new
+ end
+
+ private
+
+ def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
+ define_batchable_model(source_table, connection: ApplicationRecord.connection)
+ .where(source_key_column => start_id..stop_id)
+ .joins('INNER JOIN projects ON members.source_id = projects.id')
+ .where(type: 'ProjectMember', source_type: 'Project')
+ .where(member_namespace_id: nil)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index 82266b03c9a..84e1332cc8a 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -1,7 +1,12 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan', :smoke, feature_flag: { name: 'vue_issues_list', scope: :group } do
+ RSpec.describe(
+ 'Plan',
+ :smoke,
+ feature_flag: { name: 'vue_issues_list', scope: :group },
+ quarantine: { issue: 'https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7099', type: :investigating, only: { subdomain: 'pre' } }
+ ) do
describe 'Issue creation' do
let(:project) { Resource::Project.fabricate_via_api! }
let(:closed_issue) { Resource::Issue.fabricate_via_api! { |issue| issue.project = project } }
@@ -15,8 +20,7 @@ module QA
it(
'creates an issue',
:mobile,
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347989',
- except: { subdomain: 'pre' }
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347989'
) do
issue = Resource::Issue.fabricate_via_browser_ui! { |issue| issue.project = project }
@@ -33,8 +37,7 @@ module QA
it(
'closes an issue',
:mobile,
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347967',
- except: { subdomain: 'pre' }
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347967'
) do
closed_issue.visit!
@@ -71,7 +74,11 @@ module QA
# The following example is excluded from running in `review-qa-smoke` job
# as it proved to be flaky when running against Review App
# See https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/11568#note_621999351
- it 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347946', except: { subdomain: 'pre', job: 'review-qa-smoke' } do
+ it(
+ 'comments on an issue with an attachment',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347946',
+ except: { job: 'review-qa-smoke' }
+ ) do
Page::Project::Issue::Show.perform do |show|
show.comment('See attached image for scale', attachment: file_to_attach)
diff --git a/spec/lib/gitlab/background_migration/backfill_project_member_namespace_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_member_namespace_id_spec.rb
new file mode 100644
index 00000000000..ca7ca41a33e
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_project_member_namespace_id_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectMemberNamespaceId, :migration, schema: 20220516054011 do
+ let(:migration) do
+ described_class.new(start_id: 1, end_id: 10,
+ batch_table: table_name, batch_column: batch_column,
+ sub_batch_size: sub_batch_size, pause_ms: pause_ms,
+ connection: ApplicationRecord.connection)
+ end
+
+ let(:members_table) { table(:members) }
+ let(:projects_table) { table(:projects) }
+ let(:namespaces_table) { table(:namespaces) }
+
+ let(:table_name) { 'members' }
+ let(:batch_column) { :id }
+ let(:sub_batch_size) { 100 }
+ let(:pause_ms) { 0 }
+
+ subject(:perform_migration) do
+ migration.perform
+ end
+
+ before do
+ namespaces_table.create!(id: 201, name: 'group1', path: 'group1', type: 'Group')
+ namespaces_table.create!(id: 202, name: 'group2', path: 'group2', type: 'Group')
+ namespaces_table.create!(id: 300, name: 'project-namespace-1', path: 'project-namespace-1-path', type: 'Project')
+ namespaces_table.create!(id: 301, name: 'project-namespace-2', path: 'project-namespace-2-path', type: 'Project')
+ namespaces_table.create!(id: 302, name: 'project-namespace-3', path: 'project-namespace-3-path', type: 'Project')
+
+ projects_table.create!(id: 100, name: 'project1', path: 'project1', namespace_id: 202, project_namespace_id: 300)
+ projects_table.create!(id: 101, name: 'project2', path: 'project2', namespace_id: 202, project_namespace_id: 301)
+ projects_table.create!(id: 102, name: 'project3', path: 'project3', namespace_id: 202, project_namespace_id: 302)
+
+ # project1, no member namespace (fill in)
+ members_table.create!(id: 1, source_id: 100,
+ source_type: 'Project', type: 'ProjectMember',
+ member_namespace_id: nil, access_level: 10, notification_level: 3)
+ # bogus source id, no member namespace id (do nothing)
+ members_table.create!(id: 2, source_id: non_existing_record_id,
+ source_type: 'Project', type: 'ProjectMember',
+ member_namespace_id: nil, access_level: 10, notification_level: 3)
+ # project3, existing member namespace id (do nothing)
+ members_table.create!(id: 3, source_id: 102,
+ source_type: 'Project', type: 'ProjectMember',
+ member_namespace_id: 300, access_level: 10, notification_level: 3)
+
+ # Group memberships (do not change)
+ # group1, no member namespace (do nothing)
+ members_table.create!(id: 4, source_id: 201,
+ source_type: 'Namespace', type: 'GroupMember',
+ member_namespace_id: nil, access_level: 10, notification_level: 3)
+ # group2, existing member namespace (do nothing)
+ members_table.create!(id: 5, source_id: 202,
+ source_type: 'Namespace', type: 'GroupMember',
+ member_namespace_id: 201, access_level: 10, notification_level: 3)
+
+ # Project Namespace memberships (do not change)
+ # project namespace, existing member namespace (do nothing)
+ members_table.create!(id: 6, source_id: 300,
+ source_type: 'Namespace', type: 'ProjectNamespaceMember',
+ member_namespace_id: 201, access_level: 10, notification_level: 3)
+ # project namespace, not member namespace (do nothing)
+ members_table.create!(id: 7, source_id: 301,
+ source_type: 'Namespace', type: 'ProjectNamespaceMember',
+ member_namespace_id: 201, access_level: 10, notification_level: 3)
+ end
+
+ it 'backfills `member_namespace_id` for the selected records', :aggregate_failures do
+ expect(members_table.where(type: 'ProjectMember', member_namespace_id: nil).count).to eq 2
+ expect(members_table.where(type: 'GroupMember', member_namespace_id: nil).count).to eq 1
+
+ queries = ActiveRecord::QueryRecorder.new do
+ perform_migration
+ end
+
+ # rubocop:disable Layout/LineLength
+ expect(queries.count).to eq(3)
+ expect(members_table.where(type: 'ProjectMember', member_namespace_id: nil).count).to eq 1 # just the bogus one
+ expect(members_table.where(type: 'ProjectMember').pluck(:member_namespace_id)).to match_array([nil, 300, 300])
+ expect(members_table.where(type: 'GroupMember', member_namespace_id: nil).count).to eq 1
+ expect(members_table.where(type: 'GroupMember').pluck(:member_namespace_id)).to match_array([nil, 201])
+ # rubocop:enable Layout/LineLength
+ end
+
+ it 'tracks timings of queries' do
+ expect(migration.batch_metrics.timings).to be_empty
+
+ expect { perform_migration }.to change { migration.batch_metrics.timings }
+ end
+
+ context 'when given a negative pause_ms' do
+ let(:pause_ms) { -9 }
+ let(:sub_batch_size) { 2 }
+
+ it 'uses 0 as a floor for pause_ms' do
+ expect(migration).to receive(:sleep).with(0)
+
+ perform_migration
+ end
+ end
+end
diff --git a/spec/migrations/20220416054011_schedule_backfill_project_member_namespace_id_spec.rb b/spec/migrations/20220416054011_schedule_backfill_project_member_namespace_id_spec.rb
new file mode 100644
index 00000000000..2838fc9387c
--- /dev/null
+++ b/spec/migrations/20220416054011_schedule_backfill_project_member_namespace_id_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe ScheduleBackfillProjectMemberNamespaceId do
+ let_it_be(:migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ it 'schedules background jobs for each batch of project members' do
+ migrate!
+
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :members,
+ column_name: :id,
+ interval: described_class::INTERVAL
+ )
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all batched migration records' do
+ migrate!
+ schema_migrate_down!
+
+ expect(migration).not_to have_scheduled_batched_migration
+ end
+ end
+end