including ES resiliency teststags/6.6-RC1
@@ -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) { |
@@ -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"> |
@@ -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> | |||
@@ -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) |
@@ -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) { |
@@ -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) { |
@@ -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; | |||
} | |||
@@ -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); | |||
} | |||
} |
@@ -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(); |
@@ -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); | |||
} | |||
/** |
@@ -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) { |
@@ -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) { |
@@ -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(); | |||
} | |||
} | |||
@@ -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); | |||
} | |||
} | |||
} |
@@ -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(); |
@@ -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"); |
@@ -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"); |
@@ -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()); |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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)); | |||
@@ -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 |
@@ -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 { | |||
@@ -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; |
@@ -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; |
@@ -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()); | |||
} | |||
} |