Skip to content

Fix - Update routes for lost-and-found group and projects

What does this MR do?

Related to #219658 (closed)

This MR fixes the issue encountered when !33653 (merged) was deployed to GitLab.com production:

PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "index_routes_on_path"
     DETAIL:  Key (path)=(lost-and-found) already exists.

!33653 (merged) has been reverted with !34163 (merged)

We have identified the root cause for this incident:

!33653 (merged) was working on top of !31675 (merged), which was generating a unique namespace name for the lost-and-found group, but it was not checking whether the path was also unique (you can have different names that result to the same path).

That causes no issues while generating a namespace, but results on an error when a route is created if there is another route with the same path.

Description of the patch

The fix required is pretty simple: we have to make sure that the lost-and-found group has been assigned a unique path.

We ensure that by checking the routes table and making sure that a unique path is assigned to the lost-and-found group before generating the route for it and its projects by calling ensure_route!

The spec for the data migration has been updated accordingly to test against a data set, where an existing group already owns the lost-and-found path and the lost-and-found group is set with the wrong path.

By ensuring that the lost-and-found group's path is unique, we are also sure that all the routes generated for the orphaned projects that assigned to it will be also unique (as we already assign a unique sub-path to each project in !31675 (merged))

Proof of concept

As an incident was caused by the original MR, an additional sanity check was done in the local dev environment.

Starting with the data set generated by rake dev:setup, we dropped the foreign key from projects to namespaces and set one project to reference an invalid namespace.

We then rerun all the migrations from !31675 (merged) to add the FK, a Ghost user and the lost-and-found group and assign the orphaned project to it.

We then reset the Ghost User to a normal user and did everything all over again with a new Ghost user and 3 orphaned projects.

We finally reset the Ghost User once more, set 4 final projects to reference an invalid namespace and did the migrations one last time.

The result is similar to the current dataset in production with one Ghost user and a conflicting lost-and-found namespace with the same path as the one generated for the real Ghost user:

SELECT id, username, name, user_type FROM users WHERE id >= 45;
 id | username |    name    | user_type
----+----------+------------+-----------
 45 | ghost    | Ghost User |              -- Normal user
 46 | ghost1   | Ghost User |              -- Normal user
 47 | ghost2   | Ghost User |         5    -- The real Ghost User

SELECT id, owner_id, name, path FROM namespaces WHERE id > 51;
 id | owner_id |          name          |      path
----+----------+------------------------+-----------------
 52 |       45 | Ghost User             | ghost               
 53 |          | Lost and Found         | lost-and-found
 54 |       46 | Ghost User             | ghost1
 55 |          | Another Lost and Found | lost-and-found1
 56 |       47 | Ghost User             | ghost2          -- Correct path for the Ghost User's namespace
 57 |          | lost-and-found         | lost-and-found  -- Conflicting path with existing namespace. 
                                                          -- That's what caused the incident

-- The 4 orphaned projects have been assigned to Ghost User's lost-and-found  group (with unique sub-paths and names)

SELECT id, name, path, namespace_id FROM projects WHERE namespace_id = 57;
 id |   name    |   path    | namespace_id
----+-----------+-----------+--------------
  3 | Wget2_3   | wget2_3   |           57
 12 | Flight_12 | flight_12 |           57
 13 | Flight_13 | flight_13 |           57
 15 | Flight_15 | flight_15 |           57

-- But the routes for them have not been updated
--  and there is no route for the lost-and-found group 
-- That's the reason the original MR was created in the first place

SELECT id, source_id, source_type, path, name
FROM routes
WHERE (source_type = 'Namespace' AND source_id > 51)
  OR (source_id in (1, 2, 3, 10, 11, 12, 13, 15) AND source_type = 'Project');
 id | source_id | source_type |                path                |                  name
----+-----------+-------------+------------------------------------+-----------------------------------------
 70 |        52 | Namespace   | ghost                              | Ghost User
 71 |        53 | Namespace   | lost-and-found                     | Lost and Found
 23 |         1 | Project     | lost-and-found/gitlab-test_1       | Lost and Found / Gitlab Test_1
 72 |        54 | Namespace   | ghost1                             | Ghost User
 73 |        55 | Namespace   | lost-and-found1                    | Another Lost and Found
 24 |         2 | Project     | lost-and-found1/gitlab-shell_2     | Another Lost and Found / Gitlab Shell_2
 61 |        10 | Project     | lost-and-found1/underscore_10      | Another Lost and Found / Underscore_10
 62 |        11 | Project     | lost-and-found1/gitlab-test_11     | Another Lost and Found / Gitlab Test_11
 26 |         3 | Project     | gnuwget/wget2                      | Gnuwget / Wget2
 63 |        12 | Project     | reported_user_1/flight             | Minda Roob / Flight
 64 |        13 | Project     | reported_user_12/flight            | Chun Hoppe / Flight
 66 |        15 | Project     | reported_user_11/flight            | Earline Walter / Flight

After the migration in this MR is run, everything is fixed:

  1. The lost-and-found group's path has been updated to a unique one: lost-and-found2
  2. A route has been added for it
  3. The routes for the orphaned projects have been properly updated
SELECT id, owner_id, name, path FROM namespaces WHERE id > 51;
 id | owner_id |          name          |      path
----+----------+------------------------+-----------------
 52 |       45 | Ghost User             | ghost
 53 |          | Lost and Found         | lost-and-found
 54 |       46 | Ghost User             | ghost1
 55 |          | Another Lost and Found | lost-and-found1
 56 |       47 | Ghost User             | ghost2
 57 |          | lost-and-found         | lost-and-found2

SELECT id, source_id, source_type, path, name
FROM routes
WHERE (source_type = 'Namespace' AND source_id > 51)
  OR (source_id in (1, 2, 3, 10, 11, 12, 13, 15) AND source_type = 'Project');
 id | source_id | source_type |              path               |                   name
----+-----------+-------------+---------------------------------+--------------------------------------
 70 |        52 | Namespace   | ghost                           | Ghost User
 71 |        53 | Namespace   | lost-and-found                  | Lost and Found
 23 |         1 | Project     | lost-and-found/gitlab-test_1    | Lost and Found / Gitlab Test_1
 72 |        54 | Namespace   | ghost1                          | Ghost User
 73 |        55 | Namespace   | lost-and-found1                 | Another Lost and Found
 24 |         2 | Project     | lost-and-found1/gitlab-shell_2  | Another Lost and Found / Gitlab Shell_2
 61 |        10 | Project     | lost-and-found1/flight_10       | Another Lost and Found / Flight_10
 62 |        11 | Project     | lost-and-found1/typeahead-js_11 | Another Lost and Found / Typeahead.Js_11
 74 |        56 | Namespace   | ghost2                          | Ghost User
 75 |        57 | Namespace   | lost-and-found2                 | lost-and-found
 26 |         3 | Project     | lost-and-found2/wget2_3         | lost-and-found / Wget2_3
 63 |        12 | Project     | lost-and-found2/flight_12       | lost-and-found / Flight_12
 64 |        13 | Project     | lost-and-found2/flight_13       | lost-and-found / Flight_13
 66 |        15 | Project     | lost-and-found2/flight_15       | lost-and-found / Flight_15

As with the original MR, the results of this have been also checked through GitLab's web UI and everything is accessible.

You can find additional sanity checks that have been performed in the following snippet

Does this MR meet the acceptance criteria?

Conformity

Availability and Testing

Security

If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:

  • [-] Label as security and @ mention @gitlab-com/gl-security/appsec
  • [-] The MR includes necessary changes to maintain consistency between UI, API, email, or other methods
  • [-] Security reports checked/validated by a reviewer from the AppSec team
Edited by 🤖 GitLab Bot 🤖

Merge request reports