diff options
26 files changed, 443 insertions, 85 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java index 357732fa08c..b954a67e27f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java @@ -48,22 +48,20 @@ public class ComponentKeyUpdaterDao implements Dao { private static final Set<String> PROJECT_OR_MODULE_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.MODULE); - public void updateKey(DbSession dbSession, String projectUuid, String newKey) { + public void updateKey(DbSession dbSession, String projectOrModuleUuid, String newKey) { ComponentKeyUpdaterMapper mapper = dbSession.getMapper(ComponentKeyUpdaterMapper.class); if (mapper.countResourceByKey(newKey) > 0) { throw new IllegalArgumentException("Impossible to update key: a component with key \"" + newKey + "\" already exists."); } // must SELECT first everything - ResourceDto project = mapper.selectProject(projectUuid); + ResourceDto project = mapper.selectProject(projectOrModuleUuid); String projectOldKey = project.getKey(); - List<ResourceDto> resources = mapper.selectProjectResources(projectUuid); + List<ResourceDto> resources = mapper.selectProjectResources(projectOrModuleUuid); resources.add(project); // and then proceed with the batch UPDATE at once runBatchUpdateForAllResources(resources, projectOldKey, newKey, mapper); - - dbSession.commit(); } public static void checkIsProjectOrModule(ComponentDto component) { diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentKeyUpdaterMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentKeyUpdaterMapper.xml index b47e6f46b44..d7189fdb1ec 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentKeyUpdaterMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentKeyUpdaterMapper.xml @@ -21,15 +21,15 @@ <select id="selectProject" parameterType="String" resultMap="resourceResultMap"> select * from projects - where uuid=#{uuid,jdbcType=VARCHAR} + where uuid = #{uuid,jdbcType=VARCHAR} </select> <select id="selectProjectResources" parameterType="String" resultMap="resourceResultMap"> select * from projects where - root_uuid=#{rootUuid,jdbcType=VARCHAR} - and scope!='PRJ' - and enabled=${_true} + root_uuid = #{rootUuid,jdbcType=VARCHAR} + and scope != 'PRJ' + and enabled = ${_true} </select> <select id="selectDescendantProjects" parameterType="String" resultMap="resourceResultMap"> diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml index f9a99818714..fecf8cf8715 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -425,7 +425,7 @@ p.enabled=${_true} and p.copy_component_uuid is null <if test="projectUuid != null"> - and p.project_uuid=#{projectUuid,jdbcType=VARCHAR} + and p.project_uuid = #{projectUuid,jdbcType=VARCHAR} </if> </select> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java index 8dd4bbcadce..f8305553ee9 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java @@ -56,6 +56,7 @@ public class ComponentKeyUpdaterDaoTest { db.prepareDbUnit(getClass(), "shared.xml"); underTest.updateKey(dbSession, "B", "struts:core"); + dbSession.commit(); db.assertDbUnit(getClass(), "shouldUpdateKey-result.xml", "projects"); } @@ -70,7 +71,7 @@ public class ComponentKeyUpdaterDaoTest { db.components().insertComponent(newFileDto(project, inactiveDirectory).setKey("my_project:inactive_directory/file").setEnabled(false)); underTest.updateKey(dbSession, "A", "your_project"); - db.commit(); + dbSession.commit(); List<ComponentDto> result = dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, "your_project"); assertThat(result).hasSize(5).extracting(ComponentDto::getKey) diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java index 33d357e3a75..8c840c4749d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java @@ -56,7 +56,7 @@ public class ComponentCleanerService { throw new IllegalArgumentException("Only projects can be deleted"); } dbClient.purgeDao().deleteRootComponent(dbSession, project.uuid()); - projectIndexers.commitAndIndex(dbSession, singletonList(project.uuid()), ProjectIndexer.Cause.PROJECT_DELETION); + projectIndexers.commitAndIndex(dbSession, singletonList(project), ProjectIndexer.Cause.PROJECT_DELETION); } private static boolean hasNotProjectScope(ComponentDto project) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java index 1cf8b58feff..6c015fb1259 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java @@ -46,18 +46,18 @@ public class ComponentService { } // TODO should be moved to UpdateKeyAction - public void updateKey(DbSession dbSession, ComponentDto component, String newKey) { - userSession.checkComponentPermission(UserRole.ADMIN, component); - checkIsProjectOrModule(component); + public void updateKey(DbSession dbSession, ComponentDto projectOrModule, String newKey) { + userSession.checkComponentPermission(UserRole.ADMIN, projectOrModule); + checkIsProjectOrModule(projectOrModule); checkProjectOrModuleKeyFormat(newKey); - dbClient.componentKeyUpdaterDao().updateKey(dbSession, component.uuid(), newKey); - projectIndexers.commitAndIndex(dbSession, singletonList(component.uuid()), ProjectIndexer.Cause.PROJECT_KEY_UPDATE); + dbClient.componentKeyUpdaterDao().updateKey(dbSession, projectOrModule.uuid(), newKey); + projectIndexers.commitAndIndex(dbSession, singletonList(projectOrModule), ProjectIndexer.Cause.PROJECT_KEY_UPDATE); } // TODO should be moved to BulkUpdateKeyAction - public void bulkUpdateKey(DbSession dbSession, String projectUuid, String stringToReplace, String replacementString) { - dbClient.componentKeyUpdaterDao().bulkUpdateKey(dbSession, projectUuid, stringToReplace, replacementString); - projectIndexers.commitAndIndex(dbSession, singletonList(projectUuid), ProjectIndexer.Cause.PROJECT_KEY_UPDATE); + public void bulkUpdateKey(DbSession dbSession, ComponentDto projectOrModule, String stringToReplace, String replacementString) { + dbClient.componentKeyUpdaterDao().bulkUpdateKey(dbSession, projectOrModule.uuid(), stringToReplace, replacementString); + projectIndexers.commitAndIndex(dbSession, singletonList(projectOrModule), ProjectIndexer.Cause.PROJECT_KEY_UPDATE); } private static void checkProjectOrModuleKeyFormat(String key) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java index 115a7e28f4a..fdb5dddf2e3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java @@ -72,7 +72,7 @@ public class ComponentUpdater { ComponentDto componentDto = createRootComponent(dbSession, newComponent); removeDuplicatedProjects(dbSession, componentDto.getKey()); handlePermissionTemplate(dbSession, componentDto, newComponent.getOrganizationUuid(), userId); - projectIndexers.commitAndIndex(dbSession, singletonList(componentDto.uuid()), Cause.PROJECT_CREATION); + projectIndexers.commitAndIndex(dbSession, singletonList(componentDto), Cause.PROJECT_CREATION); return componentDto; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexers.java b/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexers.java index 51a9b34bd76..c582dfb73b3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexers.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexers.java @@ -20,13 +20,24 @@ package org.sonar.server.es; import java.util.Collection; +import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; public interface ProjectIndexers { /** * Commits the DB transaction and indexes the specified projects, if needed (according to * "cause" parameter). + * IMPORTANT - UUIDs must relate to projects only. Modules, directories and files are forbidden + * and will lead to lack of indexing. */ - void commitAndIndex(DbSession dbSession, Collection<String> projectUuid, ProjectIndexer.Cause cause); + void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause); + + default void commitAndIndex(DbSession dbSession, Collection<ComponentDto> projectOrModules, ProjectIndexer.Cause cause) { + Collection<String> projectUuids = projectOrModules.stream() + .map(ComponentDto::projectUuid) + .collect(MoreCollectors.toSet(projectOrModules.size())); + commitAndIndexByProjectUuids(dbSession, projectUuids, cause); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexersImpl.java b/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexersImpl.java index d26ba078d9c..553622ead1b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexersImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexersImpl.java @@ -38,7 +38,7 @@ public class ProjectIndexersImpl implements ProjectIndexers { } @Override - public void commitAndIndex(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) { + public void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) { Map<ProjectIndexer, Collection<EsQueueDto>> itemsByIndexer = new IdentityHashMap<>(); indexers.forEach(i -> itemsByIndexer.put(i, i.prepareForRecovery(dbSession, projectUuids, cause))); dbSession.commit(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java index 82735ce115f..ddfcaabc81b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java @@ -32,7 +32,6 @@ import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ServerSide; import org.sonar.core.component.ComponentKeys; import org.sonar.core.permission.ProjectPermissions; -import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; @@ -104,7 +103,7 @@ public class PermissionTemplateService { for (ComponentDto project : projects) { copyPermissions(dbSession, template, project, null); } - projectIndexers.commitAndIndex(dbSession, projects.stream().map(ComponentDto::uuid).collect(MoreCollectors.toList()), ProjectIndexer.Cause.PERMISSION_CHANGE); + projectIndexers.commitAndIndex(dbSession, projects, ProjectIndexer.Cause.PERMISSION_CHANGE); } /** diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionUpdater.java index 9238fd5e36e..126ba94a36c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionUpdater.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionUpdater.java @@ -43,7 +43,7 @@ public class PermissionUpdater { private final GroupPermissionChanger groupPermissionChanger; public PermissionUpdater(DbClient dbClient, ProjectIndexers projectIndexers, - UserPermissionChanger userPermissionChanger, GroupPermissionChanger groupPermissionChanger) { + UserPermissionChanger userPermissionChanger, GroupPermissionChanger groupPermissionChanger) { this.dbClient = dbClient; this.projectIndexers = projectIndexers; this.userPermissionChanger = userPermissionChanger; @@ -65,7 +65,7 @@ public class PermissionUpdater { dbClient.resourceDao().updateAuthorizationDate(projectId, dbSession); } - projectIndexers.commitAndIndex(dbSession, projectOrViewUuids, ProjectIndexer.Cause.PERMISSION_CHANGE); + projectIndexers.commitAndIndexByProjectUuids(dbSession, projectOrViewUuids, ProjectIndexer.Cause.PERMISSION_CHANGE); } private boolean doApply(DbSession dbSession, PermissionChange change) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkUpdateKeyAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkUpdateKeyAction.java index 890571e70ba..106a9db3a6c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkUpdateKeyAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkUpdateKeyAction.java @@ -154,8 +154,7 @@ public class BulkUpdateKeyAction implements ProjectsWsAction { } private void bulkUpdateKey(DbSession dbSession, BulkUpdateKeyWsRequest request, ComponentDto projectOrModule) { - componentService.bulkUpdateKey(dbSession, projectOrModule.uuid(), request.getFrom(), request.getTo()); - dbSession.commit(); + componentService.bulkUpdateKey(dbSession, projectOrModule, request.getFrom(), request.getTo()); } private static BulkUpdateKeyWsResponse buildResponse(Map<String, String> newKeysByOldKeys, Map<String, Boolean> newKeysWithDuplicateMap) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateKeyAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateKeyAction.java index d845d695d89..4364f3c1cac 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateKeyAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateKeyAction.java @@ -101,7 +101,6 @@ public class UpdateKeyAction implements ProjectsWsAction { try (DbSession dbSession = dbClient.openSession(false)) { ComponentDto projectOrModule = componentFinder.getByUuidOrKey(dbSession, request.getId(), request.getKey(), ParamNames.PROJECT_ID_AND_FROM); componentService.updateKey(dbSession, projectOrModule, request.getNewKey()); - dbSession.commit(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java index 990839e226c..95fb26c20e3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java @@ -109,7 +109,7 @@ public class UpdateVisibilityAction implements ProjectsWsAction { } else { updatePermissionsToPublic(dbSession, component); } - projectIndexers.commitAndIndex(dbSession, singletonList(component.uuid()), ProjectIndexer.Cause.PERMISSION_CHANGE); + projectIndexers.commitAndIndex(dbSession, singletonList(component), ProjectIndexer.Cause.PERMISSION_CHANGE); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/projecttag/ws/SetAction.java b/server/sonar-server/src/main/java/org/sonar/server/projecttag/ws/SetAction.java index 71fab7c6114..b46278cde7c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projecttag/ws/SetAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projecttag/ws/SetAction.java @@ -99,7 +99,7 @@ public class SetAction implements ProjectTagsWsAction { project.setTags(tags); dbClient.componentDao().updateTags(dbSession, project); - projectIndexers.commitAndIndex(dbSession, singletonList(project.uuid()), PROJECT_TAGS_UPDATE); + projectIndexers.commitAndIndex(dbSession, singletonList(project), PROJECT_TAGS_UPDATE); } response.noContent(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceTest.java index b8928a8419f..7e63c6ec873 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceTest.java @@ -60,7 +60,7 @@ public class ComponentServiceTest { ComponentDto file = componentDb.insertComponent(newFileDto(module, null).setKey("my_project:root:module:src/File.xoo")); ComponentDto inactiveFile = componentDb.insertComponent(newFileDto(module, null).setKey("my_project:root:module:src/InactiveFile.xoo").setEnabled(false)); - underTest.bulkUpdateKey(dbSession, project.uuid(), "my_", "your_"); + underTest.bulkUpdateKey(dbSession, project, "my_", "your_"); assertComponentKeyUpdated(project.key(), "your_project"); assertComponentKeyUpdated(module.key(), "your_project:root:module"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java index b4208705488..22334b55a58 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java @@ -86,10 +86,9 @@ public class ComponentServiceUpdateKeyTest { public void update_module_key() { ComponentDto project = insertSampleRootProject(); ComponentDto module = ComponentTesting.newModuleDto(project).setKey("sample:root:module"); - dbClient.componentDao().insert(dbSession, module); + db.components().insertComponent(module); ComponentDto file = ComponentTesting.newFileDto(module, null).setKey("sample:root:module:src/File.xoo"); - dbClient.componentDao().insert(dbSession, file); - dbSession.commit(); + db.components().insertComponent(file); logInAsProjectAdministrator(project); underTest.updateKey(dbSession, module, "sample:root2:module"); @@ -99,7 +98,8 @@ public class ComponentServiceUpdateKeyTest { assertComponentKeyHasBeenUpdated(module.key(), "sample:root2:module"); assertComponentKeyHasBeenUpdated(file.key(), "sample:root2:module:src/File.xoo"); - org.assertj.core.api.Assertions.assertThat(projectIndexers.hasBeenCalled(module.uuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue(); + // do not index the module but the project + org.assertj.core.api.Assertions.assertThat(projectIndexers.hasBeenCalled(project.uuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue(); } @Test @@ -180,7 +180,7 @@ public class ComponentServiceUpdateKeyTest { ComponentDto file = componentDb.insertComponent(newFileDto(module, null).setKey("my_project:root:module:src/File.xoo")); ComponentDto inactiveFile = componentDb.insertComponent(newFileDto(module, null).setKey("my_project:root:module:src/InactiveFile.xoo").setEnabled(false)); - underTest.bulkUpdateKey(dbSession, project.uuid(), "my_", "your_"); + underTest.bulkUpdateKey(dbSession, project, "my_", "your_"); assertComponentKeyUpdated(project.key(), "your_project"); assertComponentKeyUpdated(module.key(), "your_project:root:module"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java index 959ec117e9e..86874bb8fcc 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java @@ -184,6 +184,7 @@ public class ComponentIndexerTest { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project)); indexProject(project, PROJECT_CREATION); + assertThatIndexHasSize(2); db.getDbClient().componentDao().delete(db.getSession(), project.getId()); db.getDbClient().componentDao().delete(db.getSession(), file.getId()); diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersImplTest.java index 2d956778031..8ba6ddd9e81 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersImplTest.java @@ -19,14 +19,17 @@ */ package org.sonar.server.es; +import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Set; import org.junit.Test; import org.sonar.db.DbSession; -import org.sonar.db.es.EsQueueDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.organization.OrganizationTesting; +import org.sonar.server.es.ProjectIndexer.Cause; -import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -34,54 +37,34 @@ import static org.mockito.Mockito.mock; public class ProjectIndexersImplTest { @Test - public void commitAndIndex_calls_indexer_with_only_its_supported_items() { - EsQueueDto item1a = EsQueueDto.create("fake/fake1", "P1"); - EsQueueDto item1b = EsQueueDto.create("fake/fake1", "P1"); - EsQueueDto item2 = EsQueueDto.create("fake/fake2", "P1"); - FakeIndexer indexer1 = new FakeIndexer(asList(item1a, item1b)); - FakeIndexer indexer2 = new FakeIndexer(singletonList(item2)); - DbSession dbSession = mock(DbSession.class); + public void commitAndIndex_indexes_project() { + OrganizationDto organization = OrganizationTesting.newOrganizationDto(); + ComponentDto project = ComponentTesting.newPublicProjectDto(organization); - ProjectIndexersImpl underTest = new ProjectIndexersImpl(indexer1, indexer2); - underTest.commitAndIndex(dbSession, singletonList("P1"), ProjectIndexer.Cause.PROJECT_CREATION); + FakeIndexers underTest = new FakeIndexers(); + underTest.commitAndIndex(mock(DbSession.class), singletonList(project), Cause.PROJECT_CREATION); - assertThat(indexer1.calledItems).containsExactlyInAnyOrder(item1a, item1b); - assertThat(indexer2.calledItems).containsExactlyInAnyOrder(item2); + assertThat(underTest.calls).containsExactly(project.uuid()); } - private static class FakeIndexer implements ProjectIndexer { - - private final List<EsQueueDto> items; - private Collection<EsQueueDto> calledItems; - - private FakeIndexer(List<EsQueueDto> items) { - this.items = items; - } - - @Override - public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) { - throw new UnsupportedOperationException(); - } + @Test + public void commitAndIndex_of_module_indexes_the_project() { + OrganizationDto organization = OrganizationTesting.newOrganizationDto(); + ComponentDto project = ComponentTesting.newPublicProjectDto(organization); + ComponentDto module = ComponentTesting.newModuleDto(project); - @Override - public Set<IndexType> getIndexTypes() { - throw new UnsupportedOperationException(); - } + FakeIndexers underTest = new FakeIndexers(); + underTest.commitAndIndex(mock(DbSession.class), singletonList(module), Cause.PROJECT_CREATION); - @Override - public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, Cause cause) { - return items; - } + assertThat(underTest.calls).containsExactly(project.uuid()); + } - @Override - public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) { - this.calledItems = items; - return new IndexingResult(); - } + private static class FakeIndexers implements ProjectIndexers { + private final List<String> calls = new ArrayList<>(); @Override - public void indexOnAnalysis(String projectUuid) { - throw new UnsupportedOperationException(); + public void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, Cause cause) { + calls.addAll(projectUuids); } } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersTest.java new file mode 100644 index 00000000000..9ec69689c1b --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersTest.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package org.sonar.server.es; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.junit.Test; +import org.sonar.db.DbSession; +import org.sonar.db.es.EsQueueDto; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ProjectIndexersTest { + + @Test + public void commitAndIndexByProjectUuids_calls_indexer_with_only_its_supported_items() { + EsQueueDto item1a = EsQueueDto.create("fake/fake1", "P1"); + EsQueueDto item1b = EsQueueDto.create("fake/fake1", "P1"); + EsQueueDto item2 = EsQueueDto.create("fake/fake2", "P1"); + FakeIndexer indexer1 = new FakeIndexer(asList(item1a, item1b)); + FakeIndexer indexer2 = new FakeIndexer(singletonList(item2)); + DbSession dbSession = mock(DbSession.class); + + ProjectIndexersImpl underTest = new ProjectIndexersImpl(indexer1, indexer2); + underTest.commitAndIndexByProjectUuids(dbSession, singletonList("P1"), ProjectIndexer.Cause.PROJECT_CREATION); + + assertThat(indexer1.calledItems).containsExactlyInAnyOrder(item1a, item1b); + assertThat(indexer2.calledItems).containsExactlyInAnyOrder(item2); + } + + @Test + public void commitAndIndex_restricts_indexing_to_projects() { + // TODO + } + + private static class FakeIndexer implements ProjectIndexer { + + private final List<EsQueueDto> items; + private Collection<EsQueueDto> calledItems; + + private FakeIndexer(List<EsQueueDto> items) { + this.items = items; + } + + @Override + public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) { + throw new UnsupportedOperationException(); + } + + @Override + public Set<IndexType> getIndexTypes() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, Cause cause) { + return items; + } + + @Override + public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) { + this.calledItems = items; + return new IndexingResult(); + } + + @Override + public void indexOnAnalysis(String projectUuid) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/TestProjectIndexers.java b/server/sonar-server/src/test/java/org/sonar/server/es/TestProjectIndexers.java index 957612a0588..173ced760da 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/es/TestProjectIndexers.java +++ b/server/sonar-server/src/test/java/org/sonar/server/es/TestProjectIndexers.java @@ -29,7 +29,7 @@ public class TestProjectIndexers implements ProjectIndexers { private final ListMultimap<String, ProjectIndexer.Cause> calls = ArrayListMultimap.create(); @Override - public void commitAndIndex(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) { + public void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) { dbSession.commit(); projectUuids.forEach(projectUuid -> calls.put(projectUuid, cause)); diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java index ddf60c51689..632813f071f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java @@ -140,7 +140,7 @@ public class BulkUpdateKeyActionTest { tuple(project.key(), "your_project", false), tuple(module.key(), "your_project:root:module", false)); - verify(componentService).bulkUpdateKey(any(DbSession.class), eq(project.uuid()), eq(FROM), eq(TO)); + verify(componentService).bulkUpdateKey(any(DbSession.class), eq(project), eq(FROM), eq(TO)); } @Test @@ -150,7 +150,7 @@ public class BulkUpdateKeyActionTest { callByKey(provisionedProject.key(), provisionedProject.getKey(), newKey); - verify(componentService).bulkUpdateKey(any(DbSession.class), eq(provisionedProject.uuid()), eq(provisionedProject.getKey()), eq(newKey)); + verify(componentService).bulkUpdateKey(any(DbSession.class), eq(provisionedProject), eq(provisionedProject.getKey()), eq(newKey)); } @Test diff --git a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java index cf02b593415..ab947ec17e7 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java @@ -34,6 +34,7 @@ import org.sonarqube.tests.organization.OrganizationTest; import org.sonarqube.tests.organization.PersonalOrganizationTest; import org.sonarqube.tests.organization.RootUserOnOrganizationTest; import org.sonarqube.tests.projectAdministration.ProjectDeletionTest; +import org.sonarqube.tests.projectAdministration.ProjectKeyUpdateTest; import org.sonarqube.tests.projectAdministration.ProjectProvisioningTest; import org.sonarqube.tests.projectSearch.LeakProjectsPageTest; import org.sonarqube.tests.projectSearch.SearchProjectsTest; @@ -71,7 +72,8 @@ import static util.ItUtils.xooPlugin; SearchProjectsTest.class, RulesWsTest.class, ProjectDeletionTest.class, - ProjectProvisioningTest.class + ProjectProvisioningTest.class, + ProjectKeyUpdateTest.class }) public class Category6Suite { diff --git a/tests/src/test/java/org/sonarqube/tests/Elasticsearch.java b/tests/src/test/java/org/sonarqube/tests/Elasticsearch.java index 60df943b9b1..a19d9708a92 100644 --- a/tests/src/test/java/org/sonarqube/tests/Elasticsearch.java +++ b/tests/src/test/java/org/sonarqube/tests/Elasticsearch.java @@ -1,3 +1,22 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ package org.sonarqube.tests; import java.net.InetAddress; diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java index a1d7f61aba1..fb9572d3748 100644 --- a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java @@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static util.ItUtils.newAdminWsClient; import static util.ItUtils.projectDir; -public class ProjectKeyPageTest { +public class ProjectKeyUpdatePageTest { @ClassRule public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR; diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java new file mode 100644 index 00000000000..275552cf3ec --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java @@ -0,0 +1,254 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.sonarqube.tests.Category6Suite; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsComponents; +import org.sonarqube.ws.WsProjects; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.component.SearchProjectsRequest; +import org.sonarqube.ws.client.project.CreateRequest; +import org.sonarqube.ws.client.project.SearchWsRequest; +import org.sonarqube.ws.client.project.UpdateKeyWsRequest; +import util.ItUtils; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class ProjectKeyUpdateTest { + + @ClassRule + public static final Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public TestRule safeguard = new DisableOnDebug(Timeout.seconds(300)); + @Rule + public Tester tester = new Tester(orchestrator) + .setElasticsearchHttpPort(Category6Suite.SEARCH_HTTP_PORT); + + @After + public void tearDown() throws Exception { + unlockWritesOnProjectIndices(); + } + + @Test + public void update_key_of_provisioned_project() { + Organizations.Organization organization = tester.organizations().generate(); + WsProjects.CreateWsResponse.Project project = createProject(organization, "one", "Foo"); + + updateKey(project, "two"); + + assertThat(isInProjectsSearch(organization, "one")).isFalse(); + assertThat(isInProjectsSearch(organization, "two")).isTrue(); + assertThat(isInComponentSearch(organization, "one")).isFalse(); + assertThat(isInComponentSearch(organization, "two")).isTrue(); + assertThat(keyInComponentSearchProjects("Foo")).isEqualTo("two"); + assertThat(keysInComponentSuggestions("Foo")).containsExactly("two"); + } + + @Test + public void recover_indexing_errors_when_updating_key_of_provisioned_project() throws Exception { + Organizations.Organization organization = tester.organizations().generate(); + WsProjects.CreateWsResponse.Project project = createProject(organization, "one", "Foo"); + + lockWritesOnProjectIndices(); + + updateKey(project, "two"); + + assertThat(isInProjectsSearch(organization, "one")).isFalse(); + + // WS gets the list of projects from ES then reloads projects from db. + // That's why keys in WS responses are correct. + assertThat(isInProjectsSearch(organization, "one")).isFalse(); + assertThat(isInProjectsSearch(organization, "two")).isTrue(); + assertThat(keyInComponentSearchProjects("Foo")).isEqualTo("two"); + assertThat(keysInComponentSuggestions("Foo")).containsExactly("two"); + + // however searching by key is inconsistent + assertThat(keyInComponentSearchProjects("one")).isEqualTo("two"); + assertThat(keysInComponentSuggestions("one")).containsExactly("two"); + assertThat(keyInComponentSearchProjects("two")).isNull(); + assertThat(keysInComponentSuggestions("two")).isEmpty(); + + unlockWritesOnProjectIndices(); + + boolean recovered = false; + while (!recovered) { + // recovery daemon runs every second, see Category6Suite + Thread.sleep(1_000L); + recovered = keyInComponentSearchProjects("one") == null && + keysInComponentSuggestions("one").isEmpty() && + "two".equals(keyInComponentSearchProjects("two")) && + keysInComponentSuggestions("two").contains("two"); + } + } + + @Test + public void update_key_of_module() { + Organizations.Organization organization = tester.organizations().generate(); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"), + "sonar.organization", organization.getKey(), + "sonar.login", "admin", "sonar.password", "admin")); + + String initialKey = "com.sonarsource.it.samples:multi-modules-sample:module_a"; + String newKey = "com.sonarsource.it.samples:multi-modules-sample:module_c"; + + updateKey(initialKey, newKey); + + assertThat(isInComponentSearch(organization, initialKey)).isFalse(); + assertThat(isInComponentSearch(organization, newKey)).isTrue(); + // suggestions engine ignores one-character words, so we can't search for "Module A" + assertThat(keysInComponentSuggestions("Module")) + .contains(newKey) + .doesNotContain(initialKey); + assertThat(keysInComponentSuggestions(newKey)) + .contains(newKey) + .doesNotContain(initialKey); + assertThat(keysInComponentSuggestions(initialKey)).isEmpty(); + + } + + @Test + public void recover_indexing_errors_when_updating_key_of_module() throws Exception { + Organizations.Organization organization = tester.organizations().generate(); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"), + "sonar.organization", organization.getKey(), + "sonar.login", "admin", "sonar.password", "admin")); + + String initialKey = "com.sonarsource.it.samples:multi-modules-sample:module_a"; + String newKey = "com.sonarsource.it.samples:multi-modules-sample:module_c"; + + lockWritesOnProjectIndices(); + updateKey(initialKey, newKey); + + // api/components/search loads keys from db, so results are consistent + assertThat(isInComponentSearch(organization, initialKey)).isFalse(); + assertThat(isInComponentSearch(organization, newKey)).isTrue(); + + // key in result of suggestion engine is loaded from db, so results are ok when searching for unchanged name + assertThat(keysInComponentSuggestions("Module")) + .contains(newKey) + .doesNotContain(initialKey); + + // but searching for new key does not work + assertThat(keysInComponentSuggestions(newKey)).isEmpty(); + assertThat(keysInComponentSuggestions(initialKey)) + .isNotEmpty() + .contains(newKey /* the returned key is loaded from db, so it's correct */); + + unlockWritesOnProjectIndices(); + + boolean recovered = false; + while (!recovered) { + // recovery daemon runs every second, see Category6Suite + Thread.sleep(1_000L); + recovered = keysInComponentSuggestions(newKey).contains(newKey) && keysInComponentSuggestions(initialKey).isEmpty(); + } + + } + + private void lockWritesOnProjectIndices() throws Exception { + tester.elasticsearch().lockWrites("components"); + tester.elasticsearch().lockWrites("projectmeasures"); + } + + + private void unlockWritesOnProjectIndices() throws Exception { + tester.elasticsearch().unlockWrites("components"); + tester.elasticsearch().unlockWrites("projectmeasures"); + } + + private void updateKey(WsProjects.CreateWsResponse.Project project, String newKey) { + tester.wsClient().projects().updateKey(UpdateKeyWsRequest.builder().setKey(project.getKey()).setNewKey(newKey).build()); + } + + private void updateKey(String initialKey, String newKey) { + tester.wsClient().projects().updateKey(UpdateKeyWsRequest.builder().setKey(initialKey).setNewKey(newKey).build()); + } + + private WsProjects.CreateWsResponse.Project createProject(Organizations.Organization organization, String key, String name) { + CreateRequest createRequest = CreateRequest.builder().setKey(key).setName(name).setOrganization(organization.getKey()).build(); + return tester.wsClient().projects().create(createRequest).getProject(); + } + + /** + * Projects administration page - uses database + */ + private boolean isInProjectsSearch(Organizations.Organization organization, String key) { + WsProjects.SearchWsResponse response = tester.wsClient().projects().search( + SearchWsRequest.builder().setOrganization(organization.getKey()).setQuery(key).setQualifiers(singletonList("TRK")).build()); + return response.getComponentsCount() > 0; + } + + private boolean isInComponentSearch(Organizations.Organization organization, String key) { + org.sonarqube.ws.client.component.SearchWsRequest request = new org.sonarqube.ws.client.component.SearchWsRequest() + .setQualifiers(asList("TRK", "BRC")) + .setQuery(key) + .setOrganization(organization.getKey()); + return tester.wsClient().components().search(request).getComponentsCount() == 1L; + } + + /** + * Projects page - api/components/search_projects - uses ES + DB + */ + @CheckForNull + private String keyInComponentSearchProjects(String name) { + WsComponents.SearchProjectsWsResponse response = tester.wsClient().components().searchProjects( + SearchProjectsRequest.builder().setFilter("query=\"" + name + "\"").build()); + if (response.getComponentsCount() > 0) { + return response.getComponents(0).getKey(); + } + return null; + } + + /** + * Top-right search engine - api/components/suggestions - uses ES + DB + */ + private List<String> keysInComponentSuggestions(String name) { + GetRequest request = new GetRequest("api/components/suggestions").setParam("s", name); + WsResponse response = tester.wsClient().wsConnector().call(request); + Map<String, Object> json = ItUtils.jsonToMap(response.content()); + Collection<Map<String, Object>> results = (Collection<Map<String, Object>>) json.get("results"); + return results.stream() + .filter(map -> "TRK".equals(map.get("q")) || "BRC".equals(map.get("q"))) + .flatMap(map -> ((Collection<Map<String, Object>>) map.get("items")).stream()) + .map(map -> (String) map.get("key")) + .collect(Collectors.toList()); + } +} |