Browse Source

SONAR-10346 fix DB migration

tags/7.5
Sébastien Lesaint 6 years ago
parent
commit
fe6fcaba75

+ 112
- 65
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTable.java View File

@@ -19,27 +19,30 @@
*/
package org.sonar.server.platform.db.migration.version.v71;

import com.google.common.collect.Multimap;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.version.v63.DefaultOrganizationUuidProvider;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

public class MigrateWebhooksToWebhooksTable extends DataChange {

private static final long NO_RESOURCE_ID = -8_435_121;
private static final Logger LOGGER = Loggers.get(MigrateWebhooksToWebhooksTable.class);

private DefaultOrganizationUuidProvider defaultOrganizationUuidProvider;
@@ -53,47 +56,113 @@ public class MigrateWebhooksToWebhooksTable extends DataChange {

@Override
public void execute(Context context) throws SQLException {
Map<String, PropertyRow> rows = context
.prepareSelect("select id, prop_key, resource_id, text_value, created_at from properties where prop_key like 'sonar.webhooks%'")
Multimap<Long, PropertyRow> rows = context
.prepareSelect("select" +
" props.id, props.prop_key, props.resource_id, prj.uuid, props.text_value, props.created_at" +
" from properties props" +
" left join projects prj on prj.id = props.resource_id and prj.scope = ? and prj.qualifier = ? and prj.enabled = ?" +
" where" +
" props.prop_key like 'sonar.webhooks%'" +
" and props.text_value is not null")
.setString(1, "PRJ")
.setString(2, "TRK")
.setBoolean(3, true)
.list(row -> new PropertyRow(
row.getLong(1),
row.getString(2),
row.getLong(3),
row.getString(4),
row.getLong(5)))
row.getNullableLong(3),
row.getNullableString(4),
row.getString(5),
row.getLong(6)))
.stream()
.collect(toMap(PropertyRow::key, Function.identity()));

if (!rows.isEmpty()) {
migrateGlobalWebhooks(context, rows);
migrateProjectsWebhooks(context, rows);
context
.prepareUpsert("delete from properties where prop_key like 'sonar.webhooks.global%' or prop_key like 'sonar.webhooks.project%'")
.execute()
.commit();
.collect(MoreCollectors.index(PropertyRow::getResourceId, Function.identity()));

for (Map.Entry<Long, Collection<PropertyRow>> entry : rows.asMap().entrySet()) {
long projectId = entry.getKey();
if (projectId == NO_RESOURCE_ID) {
migrateGlobalWebhooks(context, entry.getValue());
} else {
migrateProjectsWebhooks(context, entry.getValue());
}
deleteAllWebhookProperties(context);
}
}

private void migrateProjectsWebhooks(Context context, Map<String, PropertyRow> properties) throws SQLException {
PropertyRow index = properties.get("sonar.webhooks.project");
if (index != null) {
private static void deleteAllWebhookProperties(Context context) throws SQLException {
context
.prepareUpsert("delete from properties where prop_key like 'sonar.webhooks.global%' or prop_key like 'sonar.webhooks.project%'")
.execute()
.commit();
}

private void migrateGlobalWebhooks(Context context, Collection<PropertyRow> rows) throws SQLException {
Multimap<String, PropertyRow> rowsByPropertyKey = rows.stream()
.collect(MoreCollectors.index(PropertyRow::getPropertyKey));
Optional<PropertyRow> rootProperty = rowsByPropertyKey.get("sonar.webhooks.global").stream().findFirst();
if (rootProperty.isPresent()) {
PropertyRow row = rootProperty.get();
// can't lambda due to checked exception.
for (Webhook webhook : extractProjectWebhooksFrom(context, properties, index.value().split(","))) {
for (Webhook webhook : extractGlobalWebhooksFrom(context, rowsByPropertyKey, row.value().split(","))) {
insert(context, webhook);
}
}
}

private void migrateGlobalWebhooks(Context context, Map<String, PropertyRow> properties) throws SQLException {
PropertyRow index = properties.get("sonar.webhooks.global");
if (index != null) {
// can't lambda due to checked exception.
for (Webhook webhook : extractGlobalWebhooksFrom(context, properties, index.value().split(","))) {
insert(context, webhook);
private List<Webhook> extractGlobalWebhooksFrom(Context context, Multimap<String, PropertyRow> rowsByPropertyKey, String[] values) throws SQLException {
String defaultOrganizationUuid = defaultOrganizationUuidProvider.get(context);
return Arrays.stream(values)
.map(value -> {
Optional<PropertyRow> name = rowsByPropertyKey.get("sonar.webhooks.global." + value + ".name").stream().findFirst();
Optional<PropertyRow> url = rowsByPropertyKey.get("sonar.webhooks.global." + value + ".url").stream().findFirst();
if (name.isPresent() && url.isPresent()) {
return new Webhook(
name.get(),
url.get(),
defaultOrganizationUuid,
null);
}
LOGGER.warn(
"Global webhook missing name and/or url will be deleted (name='{}', url='{}')",
name.map(PropertyRow::value).orElse(null),
url.map(PropertyRow::value).orElse(null));
return null;
})
.filter(Objects::nonNull)
.collect(toList());
}

private void migrateProjectsWebhooks(Context context, Collection<PropertyRow> rows) throws SQLException {
Multimap<String, PropertyRow> rowsByPropertyKey = rows.stream()
.collect(MoreCollectors.index(PropertyRow::getPropertyKey));
Optional<PropertyRow> rootProperty = rowsByPropertyKey.get("sonar.webhooks.project").stream().findFirst();
if (rootProperty.isPresent()) {
PropertyRow row = rootProperty.get();
if (row.getProjectUuid() == null) {
LOGGER.warn("At least one webhook referenced missing or non project resource '{}' and will be deleted", row.getResourceId());
} else {
for (Webhook webhook : extractProjectWebhooksFrom(row, rowsByPropertyKey, row.value().split(","))) {
insert(context, webhook);
}
}
}
}

private static List<Webhook> extractProjectWebhooksFrom(PropertyRow row, Multimap<String, PropertyRow> properties, String[] values) {
return Arrays.stream(values)
.map(value -> {
Optional<PropertyRow> name = properties.get("sonar.webhooks.project." + value + ".name").stream().findFirst();
Optional<PropertyRow> url = properties.get("sonar.webhooks.project." + value + ".url").stream().findFirst();
if (name.isPresent() && url.isPresent()) {
return new Webhook(name.get(), url.get(), null, row.projectUuid);
}
LOGGER.warn("Project webhook for project {} (id={}) missing name and/or url will be deleted (name='{}', url='{}')",
row.getProjectUuid(), row.getResourceId(), name.map(PropertyRow::value).orElse(null), url.map(PropertyRow::value).orElse(null));
return null;
})
.filter(Objects::nonNull)
.collect(MoreCollectors.toList());
}

private void insert(Context context, Webhook webhook) throws SQLException {
if (webhook.isValid()) {
context.prepareUpsert("insert into webhooks (uuid, name, url, organization_uuid, project_uuid, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?)")
@@ -111,47 +180,19 @@ public class MigrateWebhooksToWebhooksTable extends DataChange {
}
}

private List<Webhook> extractGlobalWebhooksFrom(Context context, Map<String, PropertyRow> properties, String[] values) throws SQLException {
String defaultOrganizationUuid = defaultOrganizationUuidProvider.get(context);
return Arrays.stream(values)
.map(value -> new Webhook(
properties.get("sonar.webhooks.global." + value + ".name"),
properties.get("sonar.webhooks.global." + value + ".url"),
defaultOrganizationUuid, null))
.collect(toList());
}

private static List<Webhook> extractProjectWebhooksFrom(Context context, Map<String, PropertyRow> properties, String[] values) throws SQLException {
List<Webhook> webhooks = new ArrayList<>();
for (String value : values) {
PropertyRow name = properties.get("sonar.webhooks.project." + value + ".name");
PropertyRow url = properties.get("sonar.webhooks.project." + value + ".url");
String projectUuid = checkNotNull(projectUuidOf(context, name), "Project was not found for property : sonar.webhooks.project.%s", value);
webhooks.add(new Webhook(name, url, null, projectUuid));
}
return webhooks;
}

@CheckForNull
private static String projectUuidOf(Context context, PropertyRow row) throws SQLException {
return context
.prepareSelect("select uuid from projects where id = ?")
.setLong(1, row.resourceId())
.list(row1 -> row1.getString(1)).stream().findFirst().orElse(null);
}

private static class PropertyRow {

private final Long id;
private final String key;
private final String propertyKey;
private final Long resourceId;
private final String projectUuid;
private final String value;
private final Long createdAt;

public PropertyRow(long id, String key, Long resourceId, String value, Long createdAt) {
private PropertyRow(long id, String propertyKey, @Nullable Long resourceId, @Nullable String projectUuid, String value, Long createdAt) {
this.id = id;
this.key = key;
this.propertyKey = propertyKey;
this.resourceId = resourceId;
this.projectUuid = projectUuid;
this.value = value;
this.createdAt = createdAt;
}
@@ -160,12 +201,17 @@ public class MigrateWebhooksToWebhooksTable extends DataChange {
return id;
}

public String key() {
return key;
public String getPropertyKey() {
return propertyKey;
}

public Long resourceId() {
return resourceId;
public long getResourceId() {
return resourceId == null ? NO_RESOURCE_ID : resourceId;
}

@CheckForNull
public String getProjectUuid() {
return projectUuid;
}

public String value() {
@@ -180,8 +226,9 @@ public class MigrateWebhooksToWebhooksTable extends DataChange {
public String toString() {
return "{" +
"id=" + id +
", key='" + key + '\'' +
", propertyKey='" + propertyKey + '\'' +
", resourceId=" + resourceId +
", projectUuid=" + projectUuid +
", value='" + value + '\'' +
", createdAt=" + createdAt +
'}';

+ 335
- 86
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTableTest.java View File

@@ -19,13 +19,23 @@
*/
package org.sonar.server.platform.db.migration.version.v71;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.TestSystem2;
import org.junit.runner.RunWith;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.CoreDbTester;
@@ -33,146 +43,220 @@ import org.sonar.server.platform.db.migration.version.v63.DefaultOrganizationUui

import static java.lang.Long.parseLong;
import static java.lang.String.valueOf;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.apache.commons.lang.RandomStringUtils.randomNumeric;
import static org.assertj.core.api.Assertions.assertThat;

@RunWith(DataProviderRunner.class)
public class MigrateWebhooksToWebhooksTableTest {
private static final long NOW = 1_500_000_000_000L;
private static final boolean ENABLED = true;
private static final boolean DISABLED = false;

@Rule
public final CoreDbTester dbTester = CoreDbTester.createForSchema(MigrateWebhooksToWebhooksTableTest.class, "migrate_webhooks.sql");
private static final long NOW = 1_500_000_000_000L;
private System2 system2 = new TestSystem2().setNow(NOW);

private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();

private MigrateWebhooksToWebhooksTable underTest = new MigrateWebhooksToWebhooksTable(dbTester.database(), new DefaultOrganizationUuidProviderImpl(), uuidFactory);

@Test
public void should_do_nothing_if_no_webhooks() throws SQLException {

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertNoMoreWebhookProperties();
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
}

@Test
public void should_migrate_one_global_webhook() throws SQLException {
String uuid = insertDefaultOrganization();
insertProperty("sonar.webhooks.global", "1", null, system2.now());
insertProperty("sonar.webhooks.global.1.name", "a webhook", null, system2.now());
insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, system2.now());
@UseDataProvider("numberOfGlobalWebhooksToMigration")
public void execute_migrates_any_number_of_global_webhook_to_default_organization(int numberOfGlobalWebhooks) throws SQLException {
String defaultOrganizationUuid = insertDefaultOrganization();
insertGlobalWebhookProperties(numberOfGlobalWebhooks);
Row[] webhooks = IntStream.range(1, numberOfGlobalWebhooks + 1)
.mapToObj(i -> insertGlobalWebhookProperty(i, "name webhook " + i, "url webhook " + i, defaultOrganizationUuid))
.map(Row::new)
.toArray(Row[]::new);

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
assertThat(selectWebhooksInDb())
.containsOnly(webhooks)
.extracting(Row::getUuid)
.doesNotContainNull();
assertNoMoreWebhookProperties();
}

Map<String, Object> migrated = dbTester.selectFirst("select * from webhooks");
assertThat(migrated.get("UUID")).isNotNull();
assertThat(migrated.get("NAME")).isEqualTo("a webhook");
assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
assertThat(migrated.get("PROJECT_UUID")).isNull();
assertThat(migrated.get("ORGANIZATION_UUID")).isEqualTo(uuid);
assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
assertThat(migrated.get("CREATED_AT")).isEqualTo(system2.now());
assertThat(migrated.get("UPDATED_AT")).isEqualTo(system2.now());
@DataProvider
public static Object[][] numberOfGlobalWebhooksToMigration() {
return new Object[][] {
{1},
{2},
{2 + new Random().nextInt(10)}
};
}

@Test
public void should_migrate_one_project_webhook() throws SQLException {
String organization = insertDefaultOrganization();
String projectId = "156";
String projectUuid = UuidFactoryFast.getInstance().create();
;
insertProject(organization, projectId, projectUuid);
public void execute_deletes_inconsistent_properties_for_global_webhook() throws SQLException {
String defaultOrganizationUuid = insertDefaultOrganization();
insertGlobalWebhookProperties(4);
insertGlobalWebhookProperty(1, null, "no name", defaultOrganizationUuid);
insertGlobalWebhookProperty(2, "no url", null, defaultOrganizationUuid);
insertGlobalWebhookProperty(3, null, null, defaultOrganizationUuid);
Webhook webhook = insertGlobalWebhookProperty(4, "name", "url", defaultOrganizationUuid);

underTest.execute();

assertThat(selectWebhooksInDb()).containsOnly(new Row(webhook));
assertNoMoreWebhookProperties();
}

insertProperty("sonar.webhooks.project", "1", projectId, system2.now());
insertProperty("sonar.webhooks.project.1.name", "a webhook", projectId, system2.now());
insertProperty("sonar.webhooks.project.1.url", "http://webhook.com", projectId, system2.now());
@Test
@UseDataProvider("DP_execute_migrates_any_number_of_webhooks_for_any_number_of_existing_project")
public void execute_migrates_any_number_of_webhooks_for_any_number_of_existing_project(int webhookCount, int projectCount) throws SQLException {
Project[] projects = IntStream.range(0, projectCount)
.mapToObj(i -> insertProject(ENABLED))
.toArray(Project[]::new);
Row[] rows = Arrays.stream(projects).flatMap(project -> {
insertProjectWebhookProperties(project, webhookCount);
return IntStream.range(1, webhookCount + 1)
.mapToObj(i -> insertProjectWebhookProperty(project, i, "name webhook " + i, "url webhook " + i))
.map(Row::new);
}).toArray(Row[]::new);

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
assertThat(selectWebhooksInDb()).containsOnly(rows);
assertNoMoreWebhookProperties();
}

Map<String, Object> migrated = dbTester.selectFirst("select * from webhooks");
assertThat(migrated.get("UUID")).isNotNull();
assertThat(migrated.get("NAME")).isEqualTo("a webhook");
assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
assertThat(migrated.get("PROJECT_UUID")).isEqualTo(projectUuid);
assertThat(migrated.get("ORGANIZATION_UUID")).isNull();
assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
assertThat(migrated.get("CREATED_AT")).isEqualTo(system2.now());
assertThat(migrated.get("UPDATED_AT")).isEqualTo(system2.now());
@DataProvider
public static Object[][] DP_execute_migrates_any_number_of_webhooks_for_any_number_of_existing_project() {
Random random = new Random();
return new Object[][] {
{1, 1},
{2, 1},
{1, 2},
{2 + random.nextInt(5), 2 + random.nextInt(5)}
};
}

@Test
public void should_migrate_global_webhooks() throws SQLException {
insertDefaultOrganization();
insertProperty("sonar.webhooks.global", "1,2", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
public void execute_delete_webhooks_of_non_existing_project() throws SQLException {
Project project = insertProject(ENABLED);
Project nonExistingProject = new Project(233, "foo");
Row[] rows = Stream.of(project, nonExistingProject)
.map(prj -> {
insertProjectWebhookProperties(prj, 1);
return insertProjectWebhookProperty(prj, 1, "name", "url");
})
.map(Row::new)
.toArray(Row[]::new);

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(2);
assertThat(selectWebhooksInDb())
.containsOnly(Arrays.stream(rows).filter(r -> Objects.equals(r.projectUuid, project.uuid)).toArray(Row[]::new));
assertNoMoreWebhookProperties();
}

@Test
public void should_migrate_only_valid_webhooks() throws SQLException {
insertDefaultOrganization();
insertProperty("sonar.webhooks.global", "1,2,3,4", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.3.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.3.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
// nothing for 4
public void execute_delete_webhooks_of_disabled_project() throws SQLException {
Project project = insertProject(ENABLED);
Project nonExistingProject = insertProject(DISABLED);
Row[] rows = Stream.of(project, nonExistingProject)
.map(prj -> {
insertProjectWebhookProperties(prj, 1);
return insertProjectWebhookProperty(prj, 1, "name", "url");
})
.map(Row::new)
.toArray(Row[]::new);

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
assertThat(selectWebhooksInDb())
.containsOnly(Arrays.stream(rows).filter(r -> Objects.equals(r.projectUuid, project.uuid)).toArray(Row[]::new));
assertNoMoreWebhookProperties();
}

@Test
public void should_migrate_project_webhooks() throws SQLException {
String organization = insertDefaultOrganization();
String projectId = "156";
String projectUuid = UuidFactoryFast.getInstance().create();
;
insertProject(organization, projectId, projectUuid);

insertProperty("sonar.webhooks.project", "1,2", projectId, system2.now());
insertProperty("sonar.webhooks.project.1.name", "a webhook", projectId, system2.now());
insertProperty("sonar.webhooks.project.1.url", "http://webhook.com", projectId, system2.now());
insertProperty("sonar.webhooks.project.2.name", "another webhook", projectId, system2.now());
insertProperty("sonar.webhooks.project.2.url", "http://webhookhookhook.com", projectId, system2.now());
public void execute_deletes_inconsistent_properties_for_project_webhook() throws SQLException {
Project project = insertProject(ENABLED);
insertProjectWebhookProperties(project, 4);
insertProjectWebhookProperty(project, 1, null, "no name");
insertProjectWebhookProperty(project, 2, "no url", null);
insertProjectWebhookProperty(project, 3, null, null);
Webhook webhook = insertProjectWebhookProperty(project, 4, "name", "url");

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(2);
assertThat(selectWebhooksInDb()).containsOnly(new Row(webhook));
assertNoMoreWebhookProperties();
}

@Test
public void should_not_migrate_more_than_10_webhooks_per_project() throws SQLException {
@UseDataProvider("DP_execute_delete_webhooks_of_components_which_is_not_a_project")
public void execute_delete_webhooks_of_components_which_is_not_a_project(int webhookCount, String scope, String qualifier) throws SQLException {
Project project = insertComponent(scope, qualifier, ENABLED);
insertProjectWebhookProperties(project, webhookCount);
IntStream.range(1, webhookCount + 1)
.forEach(i -> insertProjectWebhookProperty(project, i, "name_" + i, "url_" + i));

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
assertThat(selectWebhooksInDb()).isEmpty();
assertNoMoreWebhookProperties();
}

@DataProvider
public static Object[][] DP_execute_delete_webhooks_of_components_which_is_not_a_project() {
String[] scopes = {Scopes.DIRECTORY, Scopes.FILE};
String[] qualifiers = {Qualifiers.VIEW, Qualifiers.SUBVIEW, Qualifiers.MODULE, Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE};
int[] webhookCounts = { 1, 2, 2 + new Random().nextInt(5)};
Object[][] res = new Object[scopes.length * qualifiers.length * webhookCounts.length][3];
int i = 0;
for (int webhookCount : webhookCounts) {
for (String scope : scopes) {
for (String qualifier : qualifiers) {
res[i][0] = webhookCount;
res[i][1] = scope;
res[i][2] = qualifier;
i++;
}
}
}
return res;
}

@Test
public void should_migrate_global_webhooks() throws SQLException {
insertDefaultOrganization();
insertProperty("sonar.webhooks.global", "1,2", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.url", "http://webhook.com", null, parseLong(randomNumeric(7)));

underTest.execute();

assertNoMoreWebhookProperties();
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(2);
}

@Test
public void should_not_migrate_more_than_10_global_webhooks() throws SQLException {
public void should_migrate_only_valid_webhooks() throws SQLException {
insertDefaultOrganization();
insertProperty("sonar.webhooks.global", "1,2,3,4", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.3.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.3.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
// nothing for 4

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
assertNoMoreWebhookProperties();
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
}

private void insertProperty(String key, @Nullable String value, @Nullable String resourceId, Long date) {
@@ -180,7 +264,7 @@ public class MigrateWebhooksToWebhooksTableTest {
"id", randomNumeric(7),
"prop_key", valueOf(key),
"text_value", value,
"is_empty", value.isEmpty() ? true : false,
"is_empty", value == null || value.isEmpty(),
"resource_id", resourceId == null ? null : valueOf(resourceId),
"created_at", valueOf(date));
}
@@ -195,11 +279,176 @@ public class MigrateWebhooksToWebhooksTableTest {
return uuid;
}

private void insertProject(String organizationUuid, String projectId, String projectUuid) {
private static long PROJECT_ID_GENERATOR = new Random().nextInt(343_343);

private Project insertProject(boolean enabled) {
return insertComponent(Scopes.PROJECT, Qualifiers.PROJECT, enabled);
}

private Project insertComponent(String scope, String qualifier, boolean enabled) {
long projectId = PROJECT_ID_GENERATOR++;
Project res = new Project(projectId, "prj_" + projectId);
dbTester.executeInsert(
"PROJECTS",
"ID", projectId,
"ORGANIZATION_UUID", organizationUuid,
"UUID", projectUuid);
"ID", res.id,
"ORGANIZATION_UUID", randomAlphanumeric(15),
"UUID", res.uuid,
"ROOT_UUID", res.uuid,
"PROJECT_UUID", res.uuid,
"UUID_PATH", "." + res.uuid + ".",
"PRIVATE", new Random().nextBoolean(),
"SCOPE", scope,
"QUALIFIER", qualifier,
"ENABLED", enabled
);
return res;
}

private void insertGlobalWebhookProperties(int total) {
insertProperty("sonar.webhooks.global",
IntStream.range(0, total).map(i -> i + 1).mapToObj(String::valueOf).collect(Collectors.joining(",")),
null,
NOW);
}

private Webhook insertGlobalWebhookProperty(int i, @Nullable String name, @Nullable String url, String organizationUuid) {
long createdAt = NOW + new Random().nextInt(5_6532_999);
Webhook res = new Webhook(name, url, organizationUuid, null, createdAt);
if (name != null) {
insertProperty("sonar.webhooks.global." + i + ".name", name, null, createdAt);
}
if (url != null) {
insertProperty("sonar.webhooks.global." + i + ".url", url, null, createdAt);
}
return res;
}

private void insertProjectWebhookProperties(Project project, int total) {
insertProperty("sonar.webhooks.project",
IntStream.range(0, total).map(i -> i + 1).mapToObj(String::valueOf).collect(Collectors.joining(",")),
valueOf(project.id),
NOW);
}

private Webhook insertProjectWebhookProperty(Project project, int i, @Nullable String name, @Nullable String url) {
long createdAt = NOW + new Random().nextInt(5_6532_999);
Webhook res = new Webhook(name, url, null, project.uuid, createdAt);
if (name != null) {
insertProperty("sonar.webhooks.project." + i + ".name", name, valueOf(project.id), createdAt);
}
if (url != null) {
insertProperty("sonar.webhooks.project." + i + ".url", url, valueOf(project.id), createdAt);
}
return res;
}

private Stream<Row> selectWebhooksInDb() {
return dbTester.select("select * from webhooks").stream().map(Row::new);
}

private void assertNoMoreWebhookProperties() {
assertThat(dbTester.countSql("select count(*) from properties where prop_key like 'sonar.webhooks.%'"))
.isEqualTo(0);
}

private static final class Webhook {
@Nullable
private final String name;
@Nullable
private final String url;
@Nullable
private final String organizationUuid;
@Nullable
private final String projectUuid;
private final long createdAt;

private Webhook(@Nullable String name, @Nullable String url, @Nullable String organizationUuid, @Nullable String projectUuid, long createdAt) {
this.name = name;
this.url = url;
this.organizationUuid = organizationUuid;
this.projectUuid = projectUuid;
this.createdAt = createdAt;
}
}

private static class Row {
private final String uuid;
private final String name;
private final String url;
@Nullable
private final String organizationUuid;
@Nullable
private final String projectUuid;
private final long createdAt;
private final long updatedAt;

private Row(Map<String, Object> row) {
this.uuid = (String) row.get("UUID");
this.name = (String) row.get("NAME");
this.url = (String) row.get("URL");
this.organizationUuid = (String) row.get("ORGANIZATION_UUID");
this.projectUuid = (String) row.get("PROJECT_UUID");
this.createdAt = (Long) row.get("CREATED_AT");
this.updatedAt = (Long) row.get("UPDATED_AT");
}

private Row(Webhook webhook) {
this.uuid = "NOT KNOWN YET";
this.name = webhook.name;
this.url = webhook.url;
this.organizationUuid = webhook.organizationUuid;
this.projectUuid = webhook.projectUuid;
this.createdAt = webhook.createdAt;
this.updatedAt = webhook.createdAt;
}

public String getUuid() {
return uuid;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Row row = (Row) o;
return createdAt == row.createdAt &&
updatedAt == row.updatedAt &&
Objects.equals(name, row.name) &&
Objects.equals(url, row.url) &&
Objects.equals(organizationUuid, row.organizationUuid) &&
Objects.equals(projectUuid, row.projectUuid);
}

@Override
public int hashCode() {
return Objects.hash(name, url, organizationUuid, projectUuid, createdAt, updatedAt);
}

@Override
public String toString() {
return "Row{" +
"uuid='" + uuid + '\'' +
", name='" + name + '\'' +
", url='" + url + '\'' +
", organizationUuid='" + organizationUuid + '\'' +
", projectUuid='" + projectUuid + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

private static final class Project {
private final long id;
private final String uuid;

private Project(long id, String uuid) {
this.id = id;
this.uuid = uuid;
}
}
}

+ 5
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTableTest/migrate_webhooks.sql View File

@@ -12,17 +12,17 @@ CREATE TABLE "PROJECTS" (
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
"KEE" VARCHAR(400),
"UUID" VARCHAR(50) NOT NULL,
"UUID_PATH" VARCHAR(1500) NOT NULL,
"ROOT_UUID" VARCHAR(50) NOT NULL,
"PROJECT_UUID" VARCHAR(50) NOT NULL,
"MODULE_UUID" VARCHAR(50),
"MODULE_UUID_PATH" VARCHAR(1500),
"MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
"NAME" VARCHAR(2000),
"DESCRIPTION" VARCHAR(2000),
"PRIVATE" BOOLEAN NOT NULL,
"TAGS" VARCHAR(500),
"ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
"SCOPE" VARCHAR(3),
"QUALIFIER" VARCHAR(10),
"DEPRECATED_KEE" VARCHAR(400),

Loading…
Cancel
Save