Skip to content

Commit

Permalink
Add backwards compatibility tests (#475)
Browse files Browse the repository at this point in the history
* Add initial framework for BWC tests

Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com>

* Update developer guide with commands to run bwc tests

Signed-off-by: Mohammad Qureshi <47198598+qreshi@users.noreply.github.com>
  • Loading branch information
qreshi authored Jul 15, 2022
1 parent d5ae629 commit ca64684
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 6 deletions.
16 changes: 10 additions & 6 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,17 @@ This project uses following tools
### Building from the command line

1. `./gradlew build` builds and tests project.
1. `./gradlew run` launches a single node cluster with the `notifications` plugin installed.
1. `./gradlew run -PnumNodes=3` launches a multi-node cluster (3 nodes) with the `notifications` plugin installed.
1. `./gradlew integTest` launches a single node cluster with the `notifications` plugin installed and runs all integ tests.
1. `./gradlew integTest -PnumNodes=3` launches a multi-node cluster with the `notifications` plugin installed and runs all integ tests.
1. `./gradlew integTest -Dtests.class="*RunnerIT"` runs a single integ test class
1. `./gradlew integTest -Dtests.method="test execute * with dryrun"` runs a single integ test method
2. `./gradlew run` launches a single node cluster with the `notifications` plugin installed.
3. `./gradlew run -PnumNodes=3` launches a multi-node cluster (3 nodes) with the `notifications` plugin installed.
4. `./gradlew integTest` launches a single node cluster with the `notifications` plugin installed and runs all integ tests.
5. `./gradlew integTest -PnumNodes=3` launches a multi-node cluster with the `notifications` plugin installed and runs all integ tests.
6. `./gradlew integTest -Dtests.class="*RunnerIT"` runs a single integ test class
7. `./gradlew integTest -Dtests.method="test execute * with dryrun"` runs a single integ test method
(remember to quote the test method name if it contains spaces).
8. `./gradlew notificationsBwcCluster#mixedClusterTask` launches a cluster with three nodes of bwc version of OpenSearch with notifications and tests backwards compatibility by upgrading one of the nodes with the current version of OpenSearch with notifications, creating a mixed cluster.
9. `./gradlew notificationsBwcCluster#rollingUpgradeClusterTask` launches a cluster with three nodes of bwc version of OpenSearch with notifications and tests backwards compatibility by performing rolling upgrade of each node with the current version of OpenSearch with notifications.
10. `./gradlew notificationsBwcCluster#fullRestartClusterTask` launches a cluster with three nodes of bwc version of OpenSearch with notifications and tests backwards compatibility by performing a full restart on the cluster upgrading all the nodes with the current version of OpenSearch with notifications.
11. `./gradlew bwcTestSuite` runs all the above bwc tests combined.

When launching a cluster using above commands, logs are placed in `notifications/build/testclusters/integTest-0/logs/`.

Expand Down
2 changes: 2 additions & 0 deletions notifications/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ buildscript {
// 2.1.0-SNAPSHOT -> 2.1.0.0-SNAPSHOT
version_tokens = opensearch_version.tokenize('-')
opensearch_build = version_tokens[0] + '.0'
plugin_no_snapshot = opensearch_build
if (buildVersionQualifier) {
opensearch_build += "-${buildVersionQualifier}"
}
if (isSnapshot) {
opensearch_build += "-SNAPSHOT"
}
opensearch_no_snapshot = opensearch_version.replace("-SNAPSHOT","")
common_utils_version = System.getProperty("common_utils.version", opensearch_build)
kotlin_version = System.getProperty("kotlin.version", "1.6.10")
junit_version = System.getProperty("junit.version", "5.7.2")
Expand Down
172 changes: 172 additions & 0 deletions notifications/notifications/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import org.opensearch.gradle.test.RestIntegTestTask
import org.opensearch.gradle.testclusters.StandaloneRestIntegTestTask

import java.util.concurrent.Callable

Expand Down Expand Up @@ -182,6 +183,12 @@ integTest {
if (System.getProperty("test.debug") != null) {
jvmArgs '-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=8000'
}

if (System.getProperty("tests.rest.bwcsuite") == null) {
filter {
excludeTestsMatching "org.opensearch.integtest.bwc.*IT"
}
}
}

project.getTasks().getByName("bundlePlugin").dependsOn(findProject(":${rootProject.name}-core").tasks.getByPath(":${rootProject.name}-core:build"))
Expand Down Expand Up @@ -211,6 +218,171 @@ testClusters.integTest {
setting 'path.repo', repo.absolutePath
}

String bwcVersion = "2.0.0.0"
String baseName = "notificationsBwcCluster"

String bwcPluginsResourcePath = "src/test/resources/plugins/bwc"
String bwcNotificationsCoreFilePath = "$bwcPluginsResourcePath/notifications-core/$bwcVersion"
String bwcNotificationsFilePath = "$bwcPluginsResourcePath/notifications/$bwcVersion"
String bwcNotificationsCoreDownload = "https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/" +
"2.0.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-notifications-core-" +
bwcVersion + ".zip"
String bwcNotificationsDownload = "https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/" +
"2.0.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-notifications-" +
bwcVersion + ".zip"

2.times { i ->
testClusters {
"${baseName}$i" {
testDistribution = "INTEG_TEST"
versions = ["2.0.0", opensearch_version]
numberOfNodes = 3
plugin(provider(new Callable<RegularFile>(){
@Override
RegularFile call() throws Exception {
return new RegularFile() {
@Override
File getAsFile() {
if (new File("$project.rootDir/$bwcNotificationsCoreFilePath").exists()) {
project.delete(files("$project.rootDir/$bwcNotificationsCoreFilePath"))
}
project.mkdir bwcNotificationsCoreFilePath
ant.get(src: bwcNotificationsCoreDownload,
dest: bwcNotificationsCoreFilePath,
httpusecaches: false)
return fileTree(bwcNotificationsCoreFilePath).getSingleFile()
}
}
}
}))
plugin(provider(new Callable<RegularFile>(){
@Override
RegularFile call() throws Exception {
return new RegularFile() {
@Override
File getAsFile() {
if (new File("$project.rootDir/$bwcNotificationsFilePath").exists()) {
project.delete(files("$project.rootDir/$bwcNotificationsFilePath"))
}
project.mkdir bwcNotificationsFilePath
ant.get(src: bwcNotificationsDownload,
dest: bwcNotificationsFilePath,
httpusecaches: false)
return fileTree(bwcNotificationsFilePath).getSingleFile()
}
}
}
}))
setting 'path.repo', "${buildDir}/cluster/shared/repo/${baseName}"
setting 'http.content_type.required', 'true'
}
}
}

List<Provider<RegularFile>> plugins = []

// Ensure the artifact for the current project version is available to be used for the bwc tests
task prepareBwcTests {
dependsOn bundle
doLast {
plugins = [
project.getObjects().fileProperty().value(coreBundle.getArchiveFile()),
project.getObjects().fileProperty().value(bundle.getArchiveFile())
]
}
}

// Create two test clusters with 3 nodes of the old version
2.times {i ->
task "${baseName}#oldVersionClusterTask$i"(type: StandaloneRestIntegTestTask) {
dependsOn 'prepareBwcTests'
useCluster testClusters."${baseName}$i"
filter {
includeTestsMatching "org.opensearch.integtest.bwc.*IT"
}
systemProperty 'tests.rest.bwcsuite', 'old_cluster'
systemProperty 'tests.rest.bwcsuite_round', 'old'
systemProperty 'tests.plugin_bwc_version', bwcVersion
nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}$i".allHttpSocketURI.join(",")}")
nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}$i".getName()}")
}
}

// Upgrade one node of the old cluster to new OpenSearch version with upgraded plugin version.
// This results in a mixed cluster with 2 nodes on the old version and 1 upgraded node.
// This is also used as a one third upgraded cluster for a rolling upgrade.
task "${baseName}#mixedClusterTask"(type: StandaloneRestIntegTestTask) {
useCluster testClusters."${baseName}0"
dependsOn "${baseName}#oldVersionClusterTask0"
doFirst {
testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins)
}
filter {
includeTestsMatching "org.opensearch.integtest.bwc.*IT"
}
systemProperty 'tests.rest.bwcsuite', 'mixed_cluster'
systemProperty 'tests.rest.bwcsuite_round', 'first'
systemProperty 'tests.plugin_bwc_version', bwcVersion
nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}0".allHttpSocketURI.join(",")}")
nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}0".getName()}")
}

// Upgrade the second node to new OpenSearch version with upgraded plugin version after the first node is upgraded.
// This results in a mixed cluster with 1 node on the old version and 2 upgraded nodes.
// This is used for rolling upgrade.
task "${baseName}#twoThirdsUpgradedClusterTask"(type: StandaloneRestIntegTestTask) {
dependsOn "${baseName}#mixedClusterTask"
useCluster testClusters."${baseName}0"
doFirst {
testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins)
}
filter {
includeTestsMatching "org.opensearch.integtest.bwc.*IT"
}
systemProperty 'tests.rest.bwcsuite', 'mixed_cluster'
systemProperty 'tests.rest.bwcsuite_round', 'second'
systemProperty 'tests.plugin_bwc_version', bwcVersion
nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}0".allHttpSocketURI.join(",")}")
nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}0".getName()}")
}

// Upgrade the third node to new OpenSearch version with upgraded plugin version after the second node is upgraded.
// This results in a fully upgraded cluster.
// This is used for rolling upgrade.
task "${baseName}#rollingUpgradeClusterTask"(type: StandaloneRestIntegTestTask) {
dependsOn "${baseName}#twoThirdsUpgradedClusterTask"
useCluster testClusters."${baseName}0"
doFirst {
testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins)
}
filter {
includeTestsMatching "org.opensearch.integtest.bwc.*IT"
}
mustRunAfter "${baseName}#mixedClusterTask"
systemProperty 'tests.rest.bwcsuite', 'mixed_cluster'
systemProperty 'tests.rest.bwcsuite_round', 'third'
systemProperty 'tests.plugin_bwc_version', bwcVersion
nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}0".allHttpSocketURI.join(",")}")
nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}0".getName()}")
}

// Upgrade all the nodes of the old cluster to new OpenSearch version with upgraded plugin version
// at the same time resulting in a fully upgraded cluster.
task "${baseName}#fullRestartClusterTask"(type: StandaloneRestIntegTestTask) {
dependsOn "${baseName}#oldVersionClusterTask1"
useCluster testClusters."${baseName}1"
doFirst {
testClusters."${baseName}1".upgradeAllNodesAndPluginsToNextVersion(plugins)
}
filter {
includeTestsMatching "org.opensearch.integtest.bwc.*IT"
}
systemProperty 'tests.rest.bwcsuite', 'upgraded_cluster'
systemProperty 'tests.plugin_bwc_version', bwcVersion
nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}1".allHttpSocketURI.join(",")}")
nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}1".getName()}")
}

run {
doFirst {
// There seems to be an issue when running multi node run or integ tasks with unicast_hosts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ abstract class PluginRestTestCase : OpenSearchRestTestCase() {
return true
}

open fun preservePluginIndicesAfterTest(): Boolean = false

@Throws(IOException::class)
@After
open fun wipeAllPluginIndices() {
if (preservePluginIndicesAfterTest()) return

val pluginIndices = listOf(".opensearch-notifications-config")
val response = client().performRequest(Request("GET", "/_cat/indices?format=json&expand_wildcards=all"))
val xContentType = XContentType.fromMediaType(response.entity.contentType.value)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.integtest.bwc

import org.opensearch.common.settings.Settings
import org.opensearch.integtest.PluginRestTestCase
import org.opensearch.notifications.NotificationPlugin
import org.opensearch.rest.RestRequest
import org.opensearch.rest.RestStatus

class NotificationsBackwardsCompatibilityIT : PluginRestTestCase() {

companion object {
private val CLUSTER_TYPE = ClusterType.parse(System.getProperty("tests.rest.bwcsuite"))
private val CLUSTER_NAME = System.getProperty("tests.clustername")
}

override fun preserveIndicesUponCompletion(): Boolean = true

override fun preservePluginIndicesAfterTest(): Boolean = true

override fun restClientSettings(): Settings {
return Settings.builder()
.put(super.restClientSettings())
// increase the timeout here to 90 seconds to handle long waits for a green
// cluster health. the waits for green need to be longer than a minute to
// account for delayed shards
.put(CLIENT_SOCKET_TIMEOUT, "90s")
.build()
}

@Throws(Exception::class)
@Suppress("UNCHECKED_CAST")
fun `test backwards compatibility`() {
val uri = getPluginUri()
val responseMap = getAsMap(uri)["nodes"] as Map<String, Map<String, Any>>
val configId = randomAlphaOfLength(20)
for (response in responseMap.values) {
val plugins = response["plugins"] as List<Map<String, Any>>
val pluginNames = plugins.map { plugin -> plugin["name"] }.toSet()
when (CLUSTER_TYPE) {
ClusterType.OLD -> {
assertTrue(pluginNames.contains("opensearch-notifications-core"))
assertTrue(pluginNames.contains("opensearch-notifications"))
createTestNotificationsConfig(configId)
}
ClusterType.MIXED -> {
verifyConfigsExist(setOf(configId))
}
ClusterType.UPGRADED -> {
verifyConfigsExist(setOf(configId))
}
}
break
}
}

private enum class ClusterType {
OLD,
MIXED,
UPGRADED;

companion object {
fun parse(value: String): ClusterType {
return when (value) {
"old_cluster" -> OLD
"mixed_cluster" -> MIXED
"upgraded_cluster" -> UPGRADED
else -> throw AssertionError("Unknown cluster type: $value")
}
}
}
}

private fun getPluginUri(): String {
return when (CLUSTER_TYPE) {
ClusterType.OLD -> "_nodes/$CLUSTER_NAME-0/plugins"
ClusterType.MIXED -> {
when (System.getProperty("tests.rest.bwcsuite_round")) {
"second" -> "_nodes/$CLUSTER_NAME-1/plugins"
"third" -> "_nodes/$CLUSTER_NAME-2/plugins"
else -> "_nodes/$CLUSTER_NAME-0/plugins"
}
}
ClusterType.UPGRADED -> "_nodes/plugins"
}
}

// TODO: Add a utility method to create random config types instead of just Slack.
// This should be generally accessible to all integ tests.
private fun createTestNotificationsConfig(configId: String) {
val requestJsonString = """
{
"config_id": "$configId",
"config": {
"name": "This is a sample config name $configId",
"description": "This is a sample config description $configId",
"config_type": "slack",
"is_enabled": true,
"slack": { "url": "https://slack.domain.com/sample_slack_url#$configId" }
}
}
""".trimIndent()
val createResponse = executeRequest(
RestRequest.Method.POST.name,
"${NotificationPlugin.PLUGIN_BASE_URI}/configs",
requestJsonString,
RestStatus.OK.status
)
val createdConfigId = createResponse.get("config_id").asString
assertNotNull(createdConfigId)
Thread.sleep(100)
}

private fun verifyConfigsExist(idSet: Set<String>) {
val getConfigsResponse = executeRequest(
RestRequest.Method.GET.name,
"${NotificationPlugin.PLUGIN_BASE_URI}/configs",
"",
RestStatus.OK.status
)
val configList = getConfigsResponse.get("config_list").asJsonArray
assertEquals("Expected ${idSet.size} configs but found configList.size()", idSet.size, configList.size())
configList.forEach {
val item = it.asJsonObject
val configId = item.get("config_id").asString
assertNotNull(configId)
assertTrue(idSet.contains(configId))
}
}
}

0 comments on commit ca64684

Please sign in to comment.