blob: 0cce5e8cbca3969ba93a25e4953beb6396f92285 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.rest;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.apache.ignite.internal.testframework.matchers.HttpResponseMatcher.hasStatusCodeAndBody;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.matchesRegex;
import static org.junit.jupiter.api.Assertions.assertAll;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import java.io.IOException;
import java.net.http.HttpResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
/**
* Test for the REST endpoints in case cluster is initialized.
*/
public class ItInitializedClusterRestTest extends AbstractRestTestBase {
/** <a href="https://semver.org">semver</a> compatible regex. */
private static final String IGNITE_SEMVER_REGEX =
"(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<maintenance>\\d+)"
+ "((?<snapshot>-SNAPSHOT)|-(?<alpha>alpha\\d+)|--(?<beta>beta\\d+)|---(?<ea>ea\\d+))?";
@BeforeEach
@Override
void setUp(TestInfo testInfo) throws IOException, InterruptedException {
super.setUp(testInfo);
// For each test case the cluster is already initialized
String requestBody = "{\n"
+ " \"metaStorageNodes\": [\n"
+ " \"" + nodeNames.get(0) + "\"\n"
+ " ],\n"
+ " \"cmgNodes\": [],\n"
+ " \"clusterName\": \"cluster\",\n"
+ " \"authConfig\": {\n"
+ " \"enabled\": false\n"
+ " }\n"
+ "}";
HttpResponse<String> response = send(post("/management/v1/cluster/init", requestBody));
assertThat(response.statusCode(), is(200));
checkAllNodesStarted();
}
@Test
@DisplayName("Node configuration is available when the cluster is initialized")
void nodeConfiguration() throws IOException, InterruptedException {
// When GET /management/v1/configuration/node
HttpResponse<String> response = send(get("/management/v1/configuration/node"));
// Expect node configuration can be parsed to hocon format
Config config = ConfigFactory.parseString(response.body());
// And has rest.port config value
assertThat(config.getInt("rest.port"), is(equalTo(10300)));
}
@Test
@DisplayName("Node configuration by path is available when the cluster is initialized")
void nodeConfigurationByPath() throws IOException, InterruptedException {
// When GET /management/v1/configuration/node and path selector is "rest"
HttpResponse<String> response = send(get("/management/v1/configuration/node/rest"));
// Expect node configuration can be parsed to hocon format
Config config = ConfigFactory.parseString(response.body());
// And has rest.port config value
assertThat(config.getInt("port"), is(equalTo(10300)));
}
@Test
@DisplayName("Node configuration can be changed when the cluster is initialized")
void nodeConfigurationUpdate() throws IOException, InterruptedException {
// When PATCH /management/v1/configuration/node rest.port=10333
HttpResponse<String> pathResponse = send(patch("/management/v1/configuration/node", "rest.port=10333"));
// Then
assertThat(pathResponse.statusCode(), is(200));
// And GET /management/v1/configuration/node
HttpResponse<String> getResponse = send(get("/management/v1/configuration/node"));
// Then node configuration can be parsed to hocon format
Config config = ConfigFactory.parseString(getResponse.body());
// And rest.port is updated
assertThat(config.getInt("rest.port"), is(equalTo(10333)));
}
@Test
@DisplayName("Cluster configuration is available when the cluster is initialized")
void clusterConfiguration() throws IOException, InterruptedException {
// When GET /management/v1/configuration/cluster
HttpResponse<String> response = send(get("/management/v1/configuration/cluster"));
// Then cluster configuration is not available
assertThat(response.statusCode(), is(200));
// And configuration can be parsed to hocon format
Config config = ConfigFactory.parseString(response.body());
// And gc.batchSize can be read
assertThat(config.getInt("gc.batchSize"), is(equalTo(5)));
}
@Test
@DisplayName("Cluster configuration can be updated when the cluster is initialized")
void clusterConfigurationUpdate() throws IOException, InterruptedException {
// When PATCH /management/v1/configuration/cluster
HttpResponse<String> patchRequest = send(patch("/management/v1/configuration/cluster", "gc.batchSize=1"));
// Then
assertThat(patchRequest.statusCode(), is(200));
// And value was updated
HttpResponse<String> getResponse = send(get("/management/v1/configuration/cluster"));
assertThat(getResponse.statusCode(), is(200));
// And
Config config = ConfigFactory.parseString(getResponse.body());
assertThat(config.getInt("gc.batchSize"), is(equalTo(1)));
}
@Test
@DisplayName("Cluster configuration can not be updated if provided config did not pass the validation")
void clusterConfigurationUpdateValidation() throws IOException, InterruptedException {
// When PATCH /management/v1/configuration/cluster invalid with invalid value
HttpResponse<String> patchRequest = send(patch("/management/v1/configuration/cluster", "{\n"
+ " security.enabled:true, \n"
+ " security.authentication.providers:null\n"
+ "}"));
// Then
assertThat(
patchRequest,
hasStatusCodeAndBody(
400,
containsString(
"Validation did not pass for keys: "
+ "[security.authentication.providers, At least one provider is required.]"
)
)
);
}
@Test
@DisplayName("Cluster configuration by path is available when the cluster is initialized")
void clusterConfigurationByPath() throws IOException, InterruptedException {
// When GET /management/v1/configuration/cluster and path selector is "gc"
HttpResponse<String> response = send(get("/management/v1/configuration/cluster/gc"));
// Then cluster configuration is not available
assertThat(response.statusCode(), is(200));
// And configuration can be parsed to hocon format
Config config = ConfigFactory.parseString(response.body());
// And gc.batchSize can be read
assertThat(config.getInt("batchSize"), is(equalTo(5)));
}
@Test
@DisplayName("Logical topology is available on initialized cluster")
void logicalTopology() throws IOException, InterruptedException {
// When GET /management/v1/cluster/topology/logical
HttpResponse<String> response = send(get("/management/v1/cluster/topology/logical"));
// Then
assertThat(response.statusCode(), is(200));
assertAll(
() -> assertThat(response.body(), hasJsonPath("$", hasSize(3))),
() -> assertThat(response.body(), hasJsonPath("$[0:2].name")),
() -> assertThat(response.body(), hasJsonPath("$[0:2].id")),
() -> assertThat(response.body(), hasJsonPath("$[0:2].address.host")),
() -> assertThat(response.body(), hasJsonPath("$[0:2].address.port"))
);
}
@Test
@DisplayName("Physical topology is available on initialized cluster")
void physicalTopology() throws IOException, InterruptedException {
// When GET /management/v1/cluster/topology/physical
HttpResponse<String> response = send(get("/management/v1/cluster/topology/physical"));
// Then
assertThat(response.statusCode(), is(200));
assertAll(
() -> assertThat(response.body(), hasJsonPath("$", hasSize(3))),
() -> assertThat(response.body(), hasJsonPath("$[0:2].name")),
() -> assertThat(response.body(), hasJsonPath("$[0:2].id")),
() -> assertThat(response.body(), hasJsonPath("$[0:2].address.host")),
() -> assertThat(response.body(), hasJsonPath("$[0:2].address.port"))
);
}
@Test
@DisplayName("Cluster state is available on initialized cluster")
void clusterState() throws IOException, InterruptedException {
// When GET /management/v1/cluster/status
HttpResponse<String> response = send(get("/management/v1/cluster/state"));
// Then
assertThat(response.statusCode(), is(200));
assertAll(
() -> assertThat(response.body(), hasJsonPath("$.clusterTag.clusterName", is(equalTo("cluster")))),
() -> assertThat(response.body(), hasJsonPath("$.clusterTag.clusterId")),
() -> assertThat(response.body(), hasJsonPath("$.igniteVersion")),
() -> assertThat(response.body(), hasJsonPath("$.msNodes")),
() -> assertThat(response.body(), hasJsonPath("$.cmgNodes"))
);
}
@Test
@DisplayName("Node state is available on initialized cluster")
void nodeState() throws IOException, InterruptedException {
// When GET /management/v1/node/status
HttpResponse<String> response = send(get("/management/v1/node/state"));
// Then
assertThat(response.statusCode(), is(200));
assertAll(
() -> assertThat(response.body(), hasJsonPath("$.name")),
() -> assertThat(response.body(), hasJsonPath("$.state", is(equalTo("STARTED"))))
);
}
@Test
@DisplayName("Node version is available on initialized cluster")
void nodeVersion() throws IOException, InterruptedException {
// When GET /management/v1/node/version/
HttpResponse<String> response = send(get("/management/v1/node/version/"));
// Then
assertThat(response.statusCode(), is(200));
// And version is a semver
assertThat(response.body(), matchesRegex(IGNITE_SEMVER_REGEX));
}
}