This is an automated email from the ASF dual-hosted git repository.

sk0x50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 323c9ab45a IGNITE-16945 Implemented node version REST command. Fixes 
#1073
323c9ab45a is described below

commit 323c9ab45ae40e39150b63afc102110b688e28b2
Author: Aleksandr Pakhomov <[email protected]>
AuthorDate: Wed Sep 21 17:39:05 2022 +0300

    IGNITE-16945 Implemented node version REST command. Fixes #1073
    
    Signed-off-by: Slava Koptilin <[email protected]>
---
 .../cli/commands/node/NodeVersionCommandTest.java  | 41 +++++++++++++++++
 ...liCommandTestNotInitializedIntegrationBase.java |  6 +++
 .../internal/rest/ItGeneratedRestClientTest.java   |  5 +++
 .../cli/call/node/version/NodeVersionCall.java     | 45 +++++++++++++++++++
 .../commands/node/version/NodeVersionCommand.java  | 49 +++++++++++++++++++++
 .../node/version/NodeVersionReplCommand.java       | 51 ++++++++++++++++++++++
 .../internal/cli/commands/node/NodeCommand.java    |  3 +-
 .../cli/commands/node/NodeReplCommand.java         |  3 +-
 .../internal/rest/api/node/NodeManagementApi.java  | 12 +++++
 modules/rest/openapi/openapi.yaml                  | 19 ++++++++
 .../rest/ItInitializedClusterRestTest.java         | 17 ++++++++
 .../rest/ItNotInitializedClusterRestTest.java      | 16 +++++++
 .../rest/node/NodeManagementController.java        |  6 +++
 13 files changed, 271 insertions(+), 2 deletions(-)

diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/node/NodeVersionCommandTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/node/NodeVersionCommandTest.java
new file mode 100644
index 0000000000..f5e80c9c41
--- /dev/null
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/node/NodeVersionCommandTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.cli.commands.node;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import 
org.apache.ignite.internal.cli.commands.CliCommandTestNotInitializedIntegrationBase;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/** Test for ignite node version command. */
+public class NodeVersionCommandTest extends 
CliCommandTestNotInitializedIntegrationBase {
+    @Test
+    @DisplayName("Should display node version with provided cluster url")
+    void nodeVersion() {
+        // When
+        execute("node", "version", "--node-url", NODE_URL);
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> 
assertOutputMatches("[1-9]\\d*\\.\\d+\\.\\d+(?:-[a-zA-Z0-9]+)?\\s+")
+        );
+    }
+}
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
index b9faad25d6..216479b4e3 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
@@ -128,6 +128,12 @@ public class CliCommandTestNotInitializedIntegrationBase 
extends IntegrationTest
                 .contains(expectedOutput);
     }
 
+    protected void assertOutputMatches(String regex) {
+        assertThat(sout.toString())
+                .as("Expected command output to match regex: " + regex + " but 
it is not: " + sout.toString())
+                .matches(regex);
+    }
+
     protected void assertOutputIsEmpty() {
         assertThat(sout.toString())
                 .as("Expected command output to be empty")
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
index b9dc04f8aa..ba4961a06e 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
@@ -317,6 +317,11 @@ public class ItGeneratedRestClientTest {
         assertThat(topologyApi.physical(), hasSize(3));
     }
 
+    @Test
+    void nodeVersion() throws ApiException {
+        assertThat(nodeManagementApi.nodeVersion(), is(notNullValue()));
+    }
+
     private CompletableFuture<Ignite> startNodeAsync(TestInfo testInfo, int 
index) {
         String nodeName = testNodeName(testInfo, BASE_PORT + index);
 
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/cli/call/node/version/NodeVersionCall.java
 
b/modules/cli/src/main/java/org/apache/ignite/cli/call/node/version/NodeVersionCall.java
new file mode 100644
index 0000000000..f0bac00da2
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/cli/call/node/version/NodeVersionCall.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cli.call.node.version;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.core.call.Call;
+import org.apache.ignite.internal.cli.core.call.CallOutput;
+import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
+import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException;
+import org.apache.ignite.rest.client.api.NodeManagementApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+
+/** Call to get node version. */
+@Singleton
+public class NodeVersionCall implements Call<UrlCallInput, String> {
+    @Override
+    public CallOutput<String> execute(UrlCallInput input) {
+        try {
+            return DefaultCallOutput.success(getNodeVersion(input.getUrl()));
+        } catch (ApiException | IllegalArgumentException e) {
+            return DefaultCallOutput.failure(new IgniteCliApiException(e, 
input.getUrl()));
+        }
+    }
+
+    private String getNodeVersion(String url) throws ApiException {
+        return new NodeManagementApi(new 
ApiClient().setBasePath(url)).nodeVersion();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/version/NodeVersionCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/version/NodeVersionCommand.java
new file mode 100644
index 0000000000..31874aff42
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/version/NodeVersionCommand.java
@@ -0,0 +1,49 @@
+/*
+ * 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.cli.commands.node.version;
+
+import jakarta.inject.Inject;
+import java.util.concurrent.Callable;
+import org.apache.ignite.cli.call.node.version.NodeVersionCall;
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.node.NodeUrlProfileMixin;
+import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+
+/** Display the node build version. */
+@Command(name = "version", description = "Prints the node build version")
+public class NodeVersionCommand extends BaseCommand implements 
Callable<Integer> {
+    @Mixin
+    private NodeUrlProfileMixin nodeUrl;
+
+    @Inject
+    private NodeVersionCall nodeVersionCall;
+
+    /** {@inheritDoc} */
+    @Override
+    public Integer call() {
+        return CallExecutionPipeline.builder(nodeVersionCall)
+                .inputProvider(() -> new UrlCallInput(nodeUrl.getNodeUrl()))
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/version/NodeVersionReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/version/NodeVersionReplCommand.java
new file mode 100644
index 0000000000..e84489a4a1
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/version/NodeVersionReplCommand.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cli.commands.node.version;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.cli.call.node.version.NodeVersionCall;
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.node.NodeUrlMixin;
+import 
org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
+import org.apache.ignite.internal.cli.core.flow.builder.Flows;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+
+/** Display the node version in REPL. */
+@Command(name = "version", description = "Prints the node build version")
+public class NodeVersionReplCommand extends BaseCommand implements Runnable {
+    @Mixin
+    private NodeUrlMixin nodeUrl;
+
+    @Inject
+    private NodeVersionCall nodeVersionCall;
+
+    @Inject
+    private ConnectToClusterQuestion question;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        question.askQuestionIfNotConnected(nodeUrl.getNodeUrl())
+                .map(UrlCallInput::new)
+                .then(Flows.fromCall(nodeVersionCall))
+                .print()
+                .start();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeCommand.java
index 52f11660e5..abbe1811c6 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeCommand.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.cli.commands.node;
 
+import org.apache.ignite.cli.commands.node.version.NodeVersionCommand;
 import org.apache.ignite.internal.cli.commands.node.config.NodeConfigCommand;
 import org.apache.ignite.internal.cli.commands.node.status.NodeStatusCommand;
 import org.apache.ignite.internal.cli.deprecated.spec.NodeCommandSpec;
@@ -27,7 +28,7 @@ import picocli.CommandLine.Mixin;
  * Node command.
  */
 @Command(name = "node",
-        subcommands = {NodeConfigCommand.class, NodeStatusCommand.class},
+        subcommands = {NodeConfigCommand.class, NodeStatusCommand.class, 
NodeVersionCommand.class},
         description = "Node operations")
 public class NodeCommand {
     @Mixin
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeReplCommand.java
index 1b6f008b31..70f82aa242 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeReplCommand.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.cli.commands.node;
 
+import org.apache.ignite.cli.commands.node.version.NodeVersionReplCommand;
 import 
org.apache.ignite.internal.cli.commands.node.config.NodeConfigReplCommand;
 import 
org.apache.ignite.internal.cli.commands.node.status.NodeStatusReplCommand;
 import org.apache.ignite.internal.cli.deprecated.spec.NodeCommandSpec;
@@ -27,7 +28,7 @@ import picocli.CommandLine.Mixin;
  * Node command in REPL mode.
  */
 @Command(name = "node",
-        subcommands = {NodeConfigReplCommand.class, 
NodeStatusReplCommand.class},
+        subcommands = {NodeConfigReplCommand.class, 
NodeStatusReplCommand.class, NodeVersionReplCommand.class},
         description = "Node operations")
 public class NodeReplCommand {
     @Mixin
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/node/NodeManagementApi.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/node/NodeManagementApi.java
index 109d84eedd..de99719880 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/node/NodeManagementApi.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/node/NodeManagementApi.java
@@ -49,4 +49,16 @@ public interface NodeManagementApi {
             MediaType.PROBLEM_JSON
     })
     NodeState state();
+
+    @Get("version")
+    @Operation(operationId = "nodeVersion")
+    @ApiResponse(responseCode = "200", description = "Return node version",
+            content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = 
@Schema(type = "string")))
+    @ApiResponse(responseCode = "500", description = "Internal error",
+            content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = 
@Schema(implementation = Problem.class)))
+    @Produces({
+            MediaType.TEXT_PLAIN,
+            MediaType.PROBLEM_JSON
+    })
+    String version();
 }
diff --git a/modules/rest/openapi/openapi.yaml 
b/modules/rest/openapi/openapi.yaml
index c00ab0d877..b11b2ea498 100644
--- a/modules/rest/openapi/openapi.yaml
+++ b/modules/rest/openapi/openapi.yaml
@@ -324,6 +324,25 @@ paths:
             application/problem+json:
               schema:
                 $ref: '#/components/schemas/Problem'
+  /management/v1/node/version:
+    get:
+      tags:
+      - nodeManagement
+      operationId: nodeVersion
+      parameters: []
+      responses:
+        "200":
+          description: Return node version
+          content:
+            text/plain:
+              schema:
+                type: string
+        "500":
+          description: Internal error
+          content:
+            application/problem+json:
+              schema:
+                $ref: '#/components/schemas/Problem'
 components:
   schemas:
     ClusterNode:
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
index 5ad1d98e25..508edbe24f 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
@@ -22,6 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 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;
@@ -38,6 +39,10 @@ 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+))?";
+
     @BeforeEach
     void setUp(TestInfo testInfo) throws IOException, InterruptedException {
         super.setUp(testInfo);
@@ -235,4 +240,16 @@ public class ItInitializedClusterRestTest extends 
AbstractRestTestBase {
                 () -> 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 = 
client.send(get("/management/v1/node/version/"), BodyHandlers.ofString());
+
+        // Then
+        assertThat(response.statusCode(), is(200));
+        // And version is a semver
+        assertThat(response.body(), matchesRegex(IGNITE_SEMVER_REGEX));
+    }
 }
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItNotInitializedClusterRestTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItNotInitializedClusterRestTest.java
index b142b14d77..c38cf00f84 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItNotInitializedClusterRestTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItNotInitializedClusterRestTest.java
@@ -22,6 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 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.fasterxml.jackson.databind.ObjectMapper;
@@ -43,6 +44,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
  */
 @ExtendWith(WorkDirectoryExtension.class)
 public class ItNotInitializedClusterRestTest 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+))?";
 
     private ObjectMapper objectMapper;
 
@@ -188,4 +192,16 @@ public class ItNotInitializedClusterRestTest extends 
AbstractRestTestBase {
                         is("Cluster not initialized. Call 
/management/v1/cluster/init in order to initialize cluster"))
         );
     }
+
+    @Test
+    @DisplayName("Node version is available on not initialized cluster")
+    void nodeVersion() throws IOException, InterruptedException {
+        // When GET /management/v1/node/version/
+        HttpResponse<String> response = 
client.send(get("/management/v1/node/version/"), BodyHandlers.ofString());
+
+        // Then
+        assertThat(response.statusCode(), is(200));
+        // And version is a semver
+        assertThat(response.body(), matchesRegex(IGNITE_SEMVER_REGEX));
+    }
 }
diff --git 
a/modules/runner/src/main/java/org/apache/ignite/internal/rest/node/NodeManagementController.java
 
b/modules/runner/src/main/java/org/apache/ignite/internal/rest/node/NodeManagementController.java
index 283622485b..dbb5ade3f7 100644
--- 
a/modules/runner/src/main/java/org/apache/ignite/internal/rest/node/NodeManagementController.java
+++ 
b/modules/runner/src/main/java/org/apache/ignite/internal/rest/node/NodeManagementController.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.rest.node;
 
 import io.micronaut.http.annotation.Controller;
+import org.apache.ignite.internal.properties.IgniteProductVersion;
 import org.apache.ignite.internal.rest.api.node.NodeManagementApi;
 import org.apache.ignite.internal.rest.api.node.NodeState;
 
@@ -39,4 +40,9 @@ public class NodeManagementController implements 
NodeManagementApi {
     public NodeState state() {
         return new NodeState(nameProvider.getName(), stateProvider.getState());
     }
+
+    @Override
+    public String version() {
+        return IgniteProductVersion.CURRENT_VERSION.toString();
+    }
 }

Reply via email to