diff --git a/pom.xml b/pom.xml
index c2a2829..ca3ff05 100644
--- a/pom.xml
+++ b/pom.xml
@@ -84,7 +84,7 @@
1.9.1.Final
1.0.8.Final
- 1.65.1
+ 1.66.0
1.3.2
2.0.0
4.12.0
diff --git a/subsystem/src/main/resources/schema/grpc-subsystem_1_0.xsd b/subsystem/src/main/resources/schema/grpc-subsystem_1_0.xsd
index 97e51a1..e9e2820 100644
--- a/subsystem/src/main/resources/schema/grpc-subsystem_1_0.xsd
+++ b/subsystem/src/main/resources/schema/grpc-subsystem_1_0.xsd
@@ -19,12 +19,11 @@
targetNamespace="urn:wildfly:grpc:1.0"
xmlns="urn:wildfly:grpc:1.0"
elementFormDefault="qualified"
- attributeFormDefault="unqualified"
version="1.0">
-
+
@@ -33,7 +32,40 @@
]]>
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/subsystem/src/test/java/org/wildfly/extension/grpc/SubsystemTestCase.java b/subsystem/src/test/java/org/wildfly/extension/grpc/SubsystemTestCase.java
index ea61444..1267363 100644
--- a/subsystem/src/test/java/org/wildfly/extension/grpc/SubsystemTestCase.java
+++ b/subsystem/src/test/java/org/wildfly/extension/grpc/SubsystemTestCase.java
@@ -19,6 +19,7 @@
import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
import org.jboss.as.subsystem.test.AdditionalInitialization;
+import org.junit.Test;
/**
* @author Kabir Khan
@@ -43,4 +44,9 @@ protected String getSubsystemXsdPath() {
protected AdditionalInitialization createAdditionalInitialization() {
return AdditionalInitialization.withCapabilities("org.wildfly.weld");
}
+
+ @Test
+ public void testExpressions() throws Exception {
+ standardSubsystemTest("grpc-subsystem-expressions.xml", false);
+ }
}
diff --git a/subsystem/src/test/resources/org/wildfly/extension/grpc/grpc-subsystem-expressions.xml b/subsystem/src/test/resources/org/wildfly/extension/grpc/grpc-subsystem-expressions.xml
new file mode 100644
index 0000000..0b4b126
--- /dev/null
+++ b/subsystem/src/test/resources/org/wildfly/extension/grpc/grpc-subsystem-expressions.xml
@@ -0,0 +1,42 @@
+
+
\ No newline at end of file
diff --git a/subsystem/src/test/resources/org/wildfly/extension/grpc/grpc-subsystem-test.xml b/subsystem/src/test/resources/org/wildfly/extension/grpc/grpc-subsystem-test.xml
index 315a493..9517c81 100644
--- a/subsystem/src/test/resources/org/wildfly/extension/grpc/grpc-subsystem-test.xml
+++ b/subsystem/src/test/resources/org/wildfly/extension/grpc/grpc-subsystem-test.xml
@@ -1,6 +1,6 @@
-
\ No newline at end of file
+
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 416b3bf..49a3821 100644
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -41,6 +41,11 @@
junit
test
+
+ org.jboss
+ jboss-dmr
+ 1.7.0.Final
+
org.jboss.arquillian.junit
arquillian-junit-container
@@ -61,10 +66,21 @@
shrinkwrap-impl-base
test
+
+ org.wildfly.arquillian
+ wildfly-arquillian-common
+ 5.1.0.Beta4
+ test
+
org.wildfly.arquillian
wildfly-arquillian-container-managed
test
+
+ org.wildfly.core
+ wildfly-controller-client
+ 25.0.0.Final
+
\ No newline at end of file
diff --git a/testsuite/integration/subsystem/pom.xml b/testsuite/integration/subsystem/pom.xml
index 14512f1..924af8b 100644
--- a/testsuite/integration/subsystem/pom.xml
+++ b/testsuite/integration/subsystem/pom.xml
@@ -16,80 +16,142 @@
limitations under the License.
-->
-
- 4.0.0
+
+ 4.0.0
-
- wildfly-grpc-testsuite-integration
- org.wildfly.extras.grpc
- 0.1.4-SNAPSHOT
- ../pom.xml
-
+
+ wildfly-grpc-testsuite-integration
+ org.wildfly.extras.grpc
+ 0.1.4-SNAPSHOT
+ ../pom.xml
+
- wildfly-grpc-testsuite-integration-subsystem
- WildFly gRPC :: Test Suite :: Integration :: Subsystem
+ wildfly-grpc-testsuite-integration-subsystem
+ WildFly gRPC :: Test Suite :: Integration :: Subsystem
-
-
- ${project.groupId}
- wildfly-grpc-feature-pack
- pom
- provided
-
-
+
+
+ ${project.groupId}
+ wildfly-grpc-feature-pack
+ pom
+ provided
+
-
-
-
- org.wildfly.plugins
- wildfly-maven-plugin
-
-
-
- org.wildfly
- wildfly-galleon-pack
- ${version.wildfly}
-
-
- ${project.groupId}
- wildfly-grpc-feature-pack
- ${project.version}
-
-
-
- web-server
- jmx-remoting
-
- grpc
-
-
- ${galleon.fork.embedded}
-
- wildfly
- ${galleon.log.time}
- true
-
-
-
- server-provisioning
-
- provision
-
- compile
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- ${jboss.dist}
- ${server.jvm.args}
-
-
-
-
-
+
+ io.grpc
+ grpc-netty-shaded
+ runtime
+
+
+ io.grpc
+ grpc-protobuf
+
+
+ io.grpc
+ grpc-stub
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+ com.google.guava
+ failureaccess
+
+
+ org.wildfly.extras.grpc
+ wildfly-grpc-subsystem
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.7.1
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ 0.6.1
+
+
+ compile-proto
+
+ compile
+ compile-custom
+
+
+ grpc-java
+
+ com.google.protobuf:protoc:${version.protobuf}:exe:${os.detected.classifier}
+
+ io.grpc:protoc-gen-grpc-java:${version.grpc}:exe:${os.detected.classifier}
+
+ /home/rsigal/tmp/wildfly.grpc.v014/wildfly-grpc-feature-pack/testsuite/integration/subsystem/src/main/proto/
+ true
+
+ helloworld.proto
+ chat.proto
+
+
+
+
+
+
+ org.wildfly.plugins
+ wildfly-maven-plugin
+
+
+
+ org.wildfly
+ wildfly-galleon-pack
+ ${version.wildfly}
+
+
+ ${project.groupId}
+ wildfly-grpc-feature-pack
+ ${project.version}
+
+
+
+ web-server
+ jmx-remoting
+
+ grpc
+
+
+ ${galleon.fork.embedded}
+
+ wildfly
+ ${galleon.log.time}
+ true
+
+
+
+ server-provisioning
+
+ provision
+
+ compile
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ ${jboss.dist}
+ ${server.jvm.args}
+
+
+
+
+
\ No newline at end of file
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/GreeterServiceImpl.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/GreeterServiceImpl.java
new file mode 100644
index 0000000..c15f573
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/GreeterServiceImpl.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.helloworld;
+
+import io.grpc.stub.StreamObserver;
+
+public class GreeterServiceImpl extends GreeterGrpc.GreeterImplBase {
+
+ @Override
+ public void sayHello(HelloRequest request, StreamObserver responseObserver) {
+ String name = request.getName();
+ String message = "Hello " + name;
+ responseObserver.onNext(HelloReply.newBuilder().setMessage(message).build());
+ responseObserver.onCompleted();
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/HelloWorldParent.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/HelloWorldParent.java
new file mode 100644
index 0000000..2ec7d99
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/HelloWorldParent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.helloworld;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Test;
+
+import io.grpc.ManagedChannel;
+
+public class HelloWorldParent {
+
+ protected static final String TARGET = "localhost:9555";
+ protected static final String TARGET_HOST = "localhost";
+ protected static final int TARGET_PORT = 9555;
+
+ protected static ManagedChannel channel = null;
+ protected static GreeterGrpc.GreeterBlockingStub blockingStub;
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void hello() {
+ HelloRequest request = HelloRequest.newBuilder().setName("Bob").build();
+ HelloReply reply = blockingStub.sayHello(request);
+ Assert.assertEquals("Hello Bob", reply.getMessage());
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/OnewaySecureHelloWorldTest.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/OnewaySecureHelloWorldTest.java
new file mode 100644
index 0000000..633fbdd
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/OnewaySecureHelloWorldTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.helloworld;
+
+import java.io.InputStream;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.as.arquillian.api.ServerSetup;
+import org.jboss.as.arquillian.container.ManagementClient;
+import org.jboss.as.arquillian.setup.SnapshotServerSetupTask;
+import org.jboss.as.controller.client.helpers.Operations;
+import org.jboss.as.controller.client.helpers.Operations.CompositeOperationBuilder;
+import org.jboss.dmr.ModelNode;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.wildfly.feature.pack.grpc.test.utility.ServerReload;
+
+import io.grpc.ChannelCredentials;
+import io.grpc.Grpc;
+import io.grpc.TlsChannelCredentials;
+
+@RunWith(Arquillian.class)
+@ServerSetup(OnewaySecureHelloWorldTest.SslServerSetupTask.class)
+@RunAsClient
+public class OnewaySecureHelloWorldTest extends HelloWorldParent {
+
+ public static class SslServerSetupTask extends SnapshotServerSetupTask {
+
+ @Override
+ protected void doSetup(final ManagementClient client, final String containerId) throws Exception {
+ secureServer(client);
+ }
+ }
+
+ protected static void secureServer(final ManagementClient client) throws Exception {
+ final CompositeOperationBuilder builder = CompositeOperationBuilder.create();
+
+ // /subsystem=elytron/key-store=grpc-key-store:add(credential-reference={clear-text="secret"}, type=JKS,
+ // path="server.keystore.jks", relative-to="jboss.server.config.dir", required=false)
+ ModelNode address = Operations.createAddress("subsystem", "elytron", "key-store", "grpc-key-store");
+ ModelNode op = Operations.createAddOperation(address);
+ final ModelNode credentialRef = new ModelNode();
+ credentialRef.get("clear-text").set("secret");
+ op.get("credential-reference").set(credentialRef);
+ op.get("type").set("JKS");
+ op.get("path").set("../../../ssl/server.keystore.jks");
+ // op.get("relative-to").set("jboss.server.config.dir");
+ op.get("required").set(false);
+ builder.addStep(op);
+
+ // /subsystem=elytron/key-manager=grpc-key-manager:add(key-store=grpc-key-store,
+ // credential-reference={clear-text="secret"})
+ address = Operations.createAddress("subsystem", "elytron", "key-manager", "grpc-key-manager");
+ op = Operations.createAddOperation(address);
+ op.get("key-store").set("grpc-key-store");
+ op.get("credential-reference").set(credentialRef);
+ builder.addStep(op);
+
+ // /subsystem=elytron/server-ssl-context=grpc-ssl-context:add(cipher-suite-filter=DEFAULT, protocols=["TLSv1.2"],
+ // want-client-auth="false", need-client-auth="true", authentication-optional="false",
+ // use-cipher-suites-order="false", key-manager="grpc-key-manager",
+ // trust-manager="grpc-key-store-trust-manager")
+ address = Operations.createAddress("subsystem", "elytron", "server-ssl-context", "grpc-ssl-context");
+ op = Operations.createAddOperation(address);
+ op.get("cipher-suite-filter").set("DEFAULT");
+ final ModelNode protocols = new ModelNode().setEmptyList();
+ protocols.add("TLSv1.2");
+ op.get("protocols").set(protocols);
+ op.get("want-client-auth").set(false);
+ op.get("need-client-auth").set(true);
+ op.get("authentication-optional").set(false);
+ op.get("use-cipher-suites-order").set(false);
+ op.get("key-manager").set("grpc-key-manager");
+ builder.addStep(op);
+
+ // /subsystem=undertow/server=default-server/https-listener=https:add(socket-binding=https,
+ // ssl-context="grpc-ssl-context")
+ address = Operations.createAddress("subsystem", "undertow", "server", "default-server", "https-listener", "https");
+ op = Operations.createAddOperation(address);
+ op.get("socket-binding").set("https");
+ op.get("ssl-context").set("grpc-ssl-context");
+ builder.addStep(op);
+
+ // /subsystem=grpc:write-attribute(name=key-manager-name, value="grpc-key-manager")
+ address = Operations.createAddress("subsystem", "grpc");
+ builder.addStep(Operations.createWriteAttributeOperation(address, "key-manager-name", "grpc-key-manager"));
+
+ final var result = client.getControllerClient().execute(builder.build());
+ if (!Operations.isSuccessfulOutcome(result)) {
+ throw new RuntimeException("Failed to configure SSL context: " + Operations.getFailureDescription(result));
+ }
+ ServerReload.reloadIfRequired(client.getControllerClient());
+ }
+
+ @Deployment
+ public static Archive> createTestArchive() {
+ WebArchive war = ShrinkWrap.create(WebArchive.class, "OnewaySecureHelloWorldTest.war");
+ war.addClasses(OnewaySecureHelloWorldTest.class, GreeterServiceImpl.class);
+ war.addPackage(HelloRequest.class.getPackage());
+ war.addClass(GreeterGrpc.class);
+ // war.as(ZipExporter.class).exportTo(
+ // new File("/tmp/hello.war"), true);
+ return war;
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ InputStream trustStore = OnewaySecureHelloWorldTest.class.getClassLoader().getResourceAsStream("client.truststore.pem");
+ ChannelCredentials creds = TlsChannelCredentials.newBuilder().trustManager(trustStore).build();
+ channel = Grpc.newChannelBuilderForAddress(TARGET_HOST, TARGET_PORT, creds).build();
+ blockingStub = GreeterGrpc.newBlockingStub(channel);
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/PlaintextHelloWorldTest.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/PlaintextHelloWorldTest.java
new file mode 100644
index 0000000..489ebd2
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/PlaintextHelloWorldTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.helloworld;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+
+import io.grpc.ManagedChannelBuilder;
+
+@RunWith(Arquillian.class)
+@RunAsClient
+public class PlaintextHelloWorldTest extends HelloWorldParent {
+
+ @Deployment
+ public static Archive> createTestArchive() {
+ WebArchive war = ShrinkWrap.create(WebArchive.class, "PlaintextHelloWorldTest.war");
+ war.addClasses(PlaintextHelloWorldTest.class, GreeterServiceImpl.class);
+ war.addPackage(HelloRequest.class.getPackage());
+ war.addClass(GreeterGrpc.class);
+ // war.as(ZipExporter.class).exportTo(
+ // new File("/tmp/hello.war"), true);
+ return war;
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ channel = ManagedChannelBuilder.forTarget(TARGET).usePlaintext().build();
+ blockingStub = GreeterGrpc.newBlockingStub(channel);
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/TwowaySecureHelloWorldTest.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/TwowaySecureHelloWorldTest.java
new file mode 100644
index 0000000..5176941
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/helloworld/TwowaySecureHelloWorldTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.helloworld;
+
+import java.io.InputStream;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.as.arquillian.api.ServerSetup;
+import org.jboss.as.arquillian.container.ManagementClient;
+import org.jboss.as.arquillian.setup.SnapshotServerSetupTask;
+import org.jboss.as.controller.client.helpers.Operations;
+import org.jboss.as.controller.client.helpers.Operations.CompositeOperationBuilder;
+import org.jboss.dmr.ModelNode;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.wildfly.feature.pack.grpc.test.utility.ServerReload;
+
+import io.grpc.ChannelCredentials;
+import io.grpc.Grpc;
+import io.grpc.TlsChannelCredentials;
+
+@RunWith(Arquillian.class)
+@ServerSetup(TwowaySecureHelloWorldTest.SslServerSetupTask.class)
+@RunAsClient
+public class TwowaySecureHelloWorldTest extends HelloWorldParent {
+
+ public static class SslServerSetupTask extends SnapshotServerSetupTask {
+
+ @Override
+ protected void doSetup(final ManagementClient client, final String containerId) throws Exception {
+ secureServer(client);
+ }
+ }
+
+ protected static void secureServer(final ManagementClient client) throws Exception {
+ final CompositeOperationBuilder builder = CompositeOperationBuilder.create();
+
+ // /subsystem=elytron/key-store=grpc-key-store:add(credential-reference={clear-text="secret"}, type=JKS,
+ // path="server.keystore.jks", relative-to="jboss.server.config.dir", required=false)
+ ModelNode address = Operations.createAddress("subsystem", "elytron", "key-store", "grpc-key-store");
+ ModelNode op = Operations.createAddOperation(address);
+ final ModelNode credentialRef = new ModelNode();
+ credentialRef.get("clear-text").set("secret");
+ op.get("credential-reference").set(credentialRef);
+ op.get("type").set("JKS");
+ op.get("path").set("../../../ssl/server.keystore.jks");
+ // op.get("relative-to").set("jboss.server.config.dir");
+ op.get("required").set(false);
+ builder.addStep(op);
+
+ // /subsystem=elytron/key-store=grpc-trust-store:add(credential-reference={clear-text="secret"}, type=JKS,
+ // required=false, path="server.truststore.jks", relative-to="jboss.server.config.dir")
+ address = Operations.createAddress("subsystem", "elytron", "key-store", "grpc-trust-store");
+ op = Operations.createAddOperation(address);
+ op.get("credential-reference").set(credentialRef);
+ op.get("type").set("JKS");
+ op.get("path").set("./../../ssl/server.truststore.jks");
+ // op.get("relative-to").set("jboss.server.config.dir");
+ builder.addStep(op);
+
+ // /subsystem=elytron/key-manager=grpc-key-manager:add(key-store=grpc-key-store,
+ // credential-reference={clear-text="secret"})
+ address = Operations.createAddress("subsystem", "elytron", "key-manager", "grpc-key-manager");
+ op = Operations.createAddOperation(address);
+ op.get("key-store").set("grpc-key-store");
+ op.get("credential-reference").set(credentialRef);
+ builder.addStep(op);
+
+ // /subsystem=elytron/trust-manager=grpc-key-store-trust-manager:add(key-store="grpc-trust-store")
+ address = Operations.createAddress("subsystem", "elytron", "trust-manager", "grpc-key-store-trust-manager");
+ op = Operations.createAddOperation(address);
+ op.get("key-store").set("grpc-trust-store");
+ builder.addStep(op);
+
+ // /subsystem=elytron/server-ssl-context=grpc-ssl-context:add(cipher-suite-filter=DEFAULT, protocols=["TLSv1.2"],
+ // want-client-auth="false", need-client-auth="true", authentication-optional="false",
+ // use-cipher-suites-order="false", key-manager="grpc-key-manager",
+ // trust-manager="grpc-key-store-trust-manager")
+ address = Operations.createAddress("subsystem", "elytron", "server-ssl-context", "grpc-ssl-context");
+ op = Operations.createAddOperation(address);
+ op.get("cipher-suite-filter").set("DEFAULT");
+ final ModelNode protocols = new ModelNode().setEmptyList();
+ protocols.add("TLSv1.2");
+ op.get("protocols").set(protocols);
+ op.get("want-client-auth").set(false);
+ op.get("need-client-auth").set(true);
+ op.get("authentication-optional").set(false);
+ op.get("use-cipher-suites-order").set(false);
+ op.get("key-manager").set("grpc-key-manager");
+ op.get("trust-manager").set("grpc-key-store-trust-manager");
+ builder.addStep(op);
+
+ // /subsystem=undertow/server=default-server/https-listener=https:add(socket-binding=https,
+ // ssl-context="grpc-ssl-context")
+ address = Operations.createAddress("subsystem", "undertow", "server", "default-server", "https-listener", "https");
+ op = Operations.createAddOperation(address);
+ op.get("socket-binding").set("https");
+ op.get("ssl-context").set("grpc-ssl-context");
+ builder.addStep(op);
+
+ // /subsystem=grpc:write-attribute(name=key-manager-name, value="grpc-key-manager")
+ address = Operations.createAddress("subsystem", "grpc");
+ builder.addStep(Operations.createWriteAttributeOperation(address, "key-manager-name", "grpc-key-manager"));
+
+ final var result = client.getControllerClient().execute(builder.build());
+ if (!Operations.isSuccessfulOutcome(result)) {
+ throw new RuntimeException("Failed to configure SSL context: " + Operations.getFailureDescription(result));
+ }
+ ServerReload.reloadIfRequired(client.getControllerClient());
+ }
+
+ @Deployment
+ public static Archive> createTestArchive() {
+ WebArchive war = ShrinkWrap.create(WebArchive.class, "TwowaySecureHelloWorldTest.war");
+ war.addClasses(TwowaySecureHelloWorldTest.class, GreeterServiceImpl.class);
+ war.addPackage(HelloRequest.class.getPackage());
+ war.addClass(GreeterGrpc.class);
+ // war.as(ZipExporter.class).exportTo(
+ // new File("/tmp/hello.war"), true);
+ return war;
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+
+ ClassLoader classLoader = TwowaySecureHelloWorldTest.class.getClassLoader();
+ InputStream trustStore = classLoader.getResourceAsStream("client.truststore.pem");
+ InputStream keyStore = classLoader.getResourceAsStream("client.keystore.pem");
+ InputStream key = classLoader.getResourceAsStream("client.key.pem");
+ ChannelCredentials creds = TlsChannelCredentials.newBuilder()
+ .trustManager(trustStore)
+ .keyManager(keyStore, key)
+ .build();
+ channel = Grpc.newChannelBuilderForAddress(TARGET_HOST, TARGET_PORT, creds).build();
+ blockingStub = GreeterGrpc.newBlockingStub(channel);
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/ChatServiceImpl.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/ChatServiceImpl.java
new file mode 100644
index 0000000..abc5bff
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/ChatServiceImpl.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.stream;
+
+import org.wildfly.extension.grpc.example.chat.ChatMessage;
+import org.wildfly.extension.grpc.example.chat.ChatServiceGrpc;
+
+import io.grpc.stub.StreamObserver;
+
+public class ChatServiceImpl extends ChatServiceGrpc.ChatServiceImplBase {
+
+ @Override
+ public StreamObserver chat(final StreamObserver responseObserver) {
+ return new StreamObserver() {
+ @Override
+ public void onNext(ChatMessage request) {
+ String s = Integer.toString(Integer.valueOf(request.getMessage()) * 2);
+ ChatMessage cm = ChatMessage.newBuilder().setMessage(s).build();
+ responseObserver.onNext(cm);
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ throw new RuntimeException(t);
+ }
+ };
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/OnewaySecureStreamingTest.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/OnewaySecureStreamingTest.java
new file mode 100644
index 0000000..ee122c6
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/OnewaySecureStreamingTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.stream;
+
+import java.io.InputStream;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.as.arquillian.api.ServerSetup;
+import org.jboss.as.arquillian.container.ManagementClient;
+import org.jboss.as.arquillian.setup.SnapshotServerSetupTask;
+import org.jboss.as.controller.client.helpers.Operations;
+import org.jboss.as.controller.client.helpers.Operations.CompositeOperationBuilder;
+import org.jboss.dmr.ModelNode;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.wildfly.extension.grpc.example.chat.ChatMessage;
+import org.wildfly.extension.grpc.example.chat.ChatServiceGrpc;
+import org.wildfly.feature.pack.grpc.test.utility.ServerReload;
+
+import io.grpc.ChannelCredentials;
+import io.grpc.Grpc;
+import io.grpc.TlsChannelCredentials;
+
+@RunWith(Arquillian.class)
+@ServerSetup(OnewaySecureStreamingTest.SslServerSetupTask.class)
+@RunAsClient
+public class OnewaySecureStreamingTest extends StreamingTestParent {
+
+ public static class SslServerSetupTask extends SnapshotServerSetupTask {
+
+ @Override
+ protected void doSetup(final ManagementClient client, final String containerId) throws Exception {
+ secureServer(client);
+ }
+ }
+
+ protected static void secureServer(final ManagementClient client) throws Exception {
+ final CompositeOperationBuilder builder = CompositeOperationBuilder.create();
+
+ // /subsystem=elytron/key-store=grpc-key-store:add(credential-reference={clear-text="secret"}, type=JKS,
+ // path="server.keystore.jks", relative-to="jboss.server.config.dir", required=false)
+ ModelNode address = Operations.createAddress("subsystem", "elytron", "key-store", "grpc-key-store");
+ ModelNode op = Operations.createAddOperation(address);
+ final ModelNode credentialRef = new ModelNode();
+ credentialRef.get("clear-text").set("secret");
+ op.get("credential-reference").set(credentialRef);
+ op.get("type").set("JKS");
+ op.get("path").set("../../../ssl/server.keystore.jks");
+ // op.get("relative-to").set("jboss.server.config.dir");
+ op.get("required").set(false);
+ builder.addStep(op);
+
+ // /subsystem=elytron/key-manager=grpc-key-manager:add(key-store=grpc-key-store,
+ // credential-reference={clear-text="secret"})
+ address = Operations.createAddress("subsystem", "elytron", "key-manager", "grpc-key-manager");
+ op = Operations.createAddOperation(address);
+ op.get("key-store").set("grpc-key-store");
+ op.get("credential-reference").set(credentialRef);
+ builder.addStep(op);
+
+ // /subsystem=elytron/server-ssl-context=grpc-ssl-context:add(cipher-suite-filter=DEFAULT, protocols=["TLSv1.2"],
+ // want-client-auth="false", need-client-auth="true", authentication-optional="false",
+ // use-cipher-suites-order="false", key-manager="grpc-key-manager",
+ // trust-manager="grpc-key-store-trust-manager")
+ address = Operations.createAddress("subsystem", "elytron", "server-ssl-context", "grpc-ssl-context");
+ op = Operations.createAddOperation(address);
+ op.get("cipher-suite-filter").set("DEFAULT");
+ final ModelNode protocols = new ModelNode().setEmptyList();
+ protocols.add("TLSv1.2");
+ op.get("protocols").set(protocols);
+ op.get("want-client-auth").set(false);
+ op.get("need-client-auth").set(true);
+ op.get("authentication-optional").set(false);
+ op.get("use-cipher-suites-order").set(false);
+ op.get("key-manager").set("grpc-key-manager");
+ builder.addStep(op);
+
+ // /subsystem=undertow/server=default-server/https-listener=https:add(socket-binding=https,
+ // ssl-context="grpc-ssl-context")
+ address = Operations.createAddress("subsystem", "undertow", "server", "default-server", "https-listener", "https");
+ op = Operations.createAddOperation(address);
+ op.get("socket-binding").set("https");
+ op.get("ssl-context").set("grpc-ssl-context");
+ builder.addStep(op);
+
+ // /subsystem=grpc:write-attribute(name=key-manager-name, value="grpc-key-manager")
+ address = Operations.createAddress("subsystem", "grpc");
+ builder.addStep(Operations.createWriteAttributeOperation(address, "key-manager-name", "grpc-key-manager"));
+
+ final var result = client.getControllerClient().execute(builder.build());
+ if (!Operations.isSuccessfulOutcome(result)) {
+ throw new RuntimeException("Failed to configure SSL context: " + Operations.getFailureDescription(result));
+ }
+ ServerReload.reloadIfRequired(client.getControllerClient());
+ }
+
+ @Deployment
+ public static Archive> createTestArchive() {
+ WebArchive war = ShrinkWrap.create(WebArchive.class, "OnewaySecureStreamingTest.war");
+ war.addClasses(OnewaySecureStreamingTest.class, ChatServiceImpl.class);
+ war.addPackage(ChatMessage.class.getPackage());
+ // war.as(ZipExporter.class).exportTo(
+ // new File("/tmp/hello.war"), true);
+ return war;
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ InputStream trustStore = OnewaySecureStreamingTest.class.getClassLoader().getResourceAsStream("client.truststore.pem");
+ ChannelCredentials creds = TlsChannelCredentials.newBuilder().trustManager(trustStore).build();
+ channel = Grpc.newChannelBuilderForAddress(TARGET_HOST, TARGET_PORT, creds).build();
+ stub = ChatServiceGrpc.newStub(channel);
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/PlaintextStreamingTest.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/PlaintextStreamingTest.java
new file mode 100644
index 0000000..2d5a976
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/PlaintextStreamingTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.stream;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.wildfly.extension.grpc.example.chat.ChatMessage;
+import org.wildfly.extension.grpc.example.chat.ChatServiceGrpc;
+
+import io.grpc.ManagedChannelBuilder;
+
+@RunWith(Arquillian.class)
+@RunAsClient
+public class PlaintextStreamingTest extends StreamingTestParent {
+
+ @Deployment
+ public static Archive> createTestArchive() {
+ WebArchive war = ShrinkWrap.create(WebArchive.class, "PlaintextStreamingTest.war");
+ war.addClasses(PlaintextStreamingTest.class, ChatServiceImpl.class);
+ war.addPackage(ChatMessage.class.getPackage());
+ // war.as(ZipExporter.class).exportTo(
+ // new File("/tmp/hello.war"), true);
+ return war;
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ channel = ManagedChannelBuilder.forTarget(TARGET).usePlaintext().build();
+ stub = ChatServiceGrpc.newStub(channel);
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/StreamingTestParent.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/StreamingTestParent.java
new file mode 100644
index 0000000..dea1bba
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/StreamingTestParent.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.stream;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Test;
+import org.wildfly.extension.grpc.example.chat.ChatMessage;
+import org.wildfly.extension.grpc.example.chat.ChatServiceGrpc;
+
+import io.grpc.ManagedChannel;
+import io.grpc.stub.StreamObserver;
+
+public class StreamingTestParent {
+
+ protected static final String TARGET = "localhost:9555";
+ protected static final String TARGET_HOST = "localhost";
+ protected static final int TARGET_PORT = 9555;
+
+ protected static ManagedChannel channel = null;
+ protected static ChatServiceGrpc.ChatServiceStub stub;
+ protected int sum;
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void streamingTest() throws InterruptedException {
+
+ final CountDownLatch finishLatch = new CountDownLatch(1);
+ StreamObserver responseObserver = new StreamObserver() {
+
+ @Override
+ public void onNext(ChatMessage message) {
+ sum += Integer.valueOf(message.getMessage());
+ }
+
+ @Override
+ public void onCompleted() {
+ finishLatch.countDown();
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ finishLatch.countDown();
+ }
+ };
+ StreamObserver requestObserver = stub.chat(responseObserver);
+ try {
+ for (int i = 0; i < 5; i++) {
+ requestObserver.onNext(ChatMessage.newBuilder().setMessage(Integer.toString(i)).build());
+ if (finishLatch.getCount() == 0) {
+ return;
+ }
+ }
+ } catch (RuntimeException e) {
+ requestObserver.onError(e);
+ throw e;
+ }
+ requestObserver.onCompleted();
+
+ if (!finishLatch.await(1, TimeUnit.MINUTES)) {
+ Assert.fail("test can not finish within 1 minute");
+ }
+ Assert.assertEquals(20, sum);
+ }
+
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/TwowaySecureStreamingTest.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/TwowaySecureStreamingTest.java
new file mode 100644
index 0000000..ce20fc6
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/stream/TwowaySecureStreamingTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.stream;
+
+import java.io.InputStream;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.as.arquillian.api.ServerSetup;
+import org.jboss.as.arquillian.container.ManagementClient;
+import org.jboss.as.arquillian.setup.SnapshotServerSetupTask;
+import org.jboss.as.controller.client.helpers.Operations;
+import org.jboss.as.controller.client.helpers.Operations.CompositeOperationBuilder;
+import org.jboss.dmr.ModelNode;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.wildfly.extension.grpc.example.chat.ChatMessage;
+import org.wildfly.extension.grpc.example.chat.ChatServiceGrpc;
+import org.wildfly.feature.pack.grpc.test.utility.ServerReload;
+
+import io.grpc.ChannelCredentials;
+import io.grpc.Grpc;
+import io.grpc.TlsChannelCredentials;
+
+@RunWith(Arquillian.class)
+@ServerSetup(TwowaySecureStreamingTest.SslServerSetupTask.class)
+@RunAsClient
+public class TwowaySecureStreamingTest extends StreamingTestParent {
+
+ public static class SslServerSetupTask extends SnapshotServerSetupTask {
+
+ @Override
+ protected void doSetup(final ManagementClient client, final String containerId) throws Exception {
+ secureServer(client);
+ }
+ }
+
+ protected static void secureServer(final ManagementClient client) throws Exception {
+ final CompositeOperationBuilder builder = CompositeOperationBuilder.create();
+
+ // /subsystem=elytron/key-store=grpc-key-store:add(credential-reference={clear-text="secret"}, type=JKS,
+ // path="server.keystore.jks", relative-to="jboss.server.config.dir", required=false)
+ ModelNode address = Operations.createAddress("subsystem", "elytron", "key-store", "grpc-key-store");
+ ModelNode op = Operations.createAddOperation(address);
+ final ModelNode credentialRef = new ModelNode();
+ credentialRef.get("clear-text").set("secret");
+ op.get("credential-reference").set(credentialRef);
+ op.get("type").set("JKS");
+ op.get("path").set("../../../ssl/server.keystore.jks");
+ // op.get("relative-to").set("jboss.server.config.dir");
+ op.get("required").set(false);
+ builder.addStep(op);
+
+ // /subsystem=elytron/key-store=grpc-trust-store:add(credential-reference={clear-text="secret"}, type=JKS,
+ // required=false, path="server.truststore.jks", relative-to="jboss.server.config.dir")
+ address = Operations.createAddress("subsystem", "elytron", "key-store", "grpc-trust-store");
+ op = Operations.createAddOperation(address);
+ op.get("credential-reference").set(credentialRef);
+ op.get("type").set("JKS");
+ op.get("path").set("./../../ssl/server.truststore.jks");
+ // op.get("relative-to").set("jboss.server.config.dir");
+ builder.addStep(op);
+
+ // /subsystem=elytron/key-manager=grpc-key-manager:add(key-store=grpc-key-store,
+ // credential-reference={clear-text="secret"})
+ address = Operations.createAddress("subsystem", "elytron", "key-manager", "grpc-key-manager");
+ op = Operations.createAddOperation(address);
+ op.get("key-store").set("grpc-key-store");
+ op.get("credential-reference").set(credentialRef);
+ builder.addStep(op);
+
+ // /subsystem=elytron/trust-manager=grpc-key-store-trust-manager:add(key-store="grpc-trust-store")
+ address = Operations.createAddress("subsystem", "elytron", "trust-manager", "grpc-key-store-trust-manager");
+ op = Operations.createAddOperation(address);
+ op.get("key-store").set("grpc-trust-store");
+ builder.addStep(op);
+
+ // /subsystem=elytron/server-ssl-context=grpc-ssl-context:add(cipher-suite-filter=DEFAULT, protocols=["TLSv1.2"],
+ // want-client-auth="false", need-client-auth="true", authentication-optional="false",
+ // use-cipher-suites-order="false", key-manager="grpc-key-manager",
+ // trust-manager="grpc-key-store-trust-manager")
+ address = Operations.createAddress("subsystem", "elytron", "server-ssl-context", "grpc-ssl-context");
+ op = Operations.createAddOperation(address);
+ op.get("cipher-suite-filter").set("DEFAULT");
+ final ModelNode protocols = new ModelNode().setEmptyList();
+ protocols.add("TLSv1.2");
+ op.get("protocols").set(protocols);
+ op.get("want-client-auth").set(false);
+ op.get("need-client-auth").set(true);
+ op.get("authentication-optional").set(false);
+ op.get("use-cipher-suites-order").set(false);
+ op.get("key-manager").set("grpc-key-manager");
+ op.get("trust-manager").set("grpc-key-store-trust-manager");
+ builder.addStep(op);
+
+ // /subsystem=undertow/server=default-server/https-listener=https:add(socket-binding=https,
+ // ssl-context="grpc-ssl-context")
+ address = Operations.createAddress("subsystem", "undertow", "server", "default-server", "https-listener", "https");
+ op = Operations.createAddOperation(address);
+ op.get("socket-binding").set("https");
+ op.get("ssl-context").set("grpc-ssl-context");
+ builder.addStep(op);
+
+ // /subsystem=grpc:write-attribute(name=key-manager-name, value="grpc-key-manager")
+ address = Operations.createAddress("subsystem", "grpc");
+ builder.addStep(Operations.createWriteAttributeOperation(address, "key-manager-name", "grpc-key-manager"));
+
+ final var result = client.getControllerClient().execute(builder.build());
+ if (!Operations.isSuccessfulOutcome(result)) {
+ throw new RuntimeException("Failed to configure SSL context: " + Operations.getFailureDescription(result));
+ }
+ ServerReload.reloadIfRequired(client.getControllerClient());
+ }
+
+ @Deployment
+ public static Archive> createTestArchive() {
+ WebArchive war = ShrinkWrap.create(WebArchive.class, "TwowaySecureStreamingTest.war");
+ war.addClasses(OnewaySecureStreamingTest.class, ChatServiceImpl.class);
+ war.addPackage(ChatMessage.class.getPackage());
+ // war.as(ZipExporter.class).exportTo(
+ // new File("/tmp/hello.war"), true);
+ return war;
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ ClassLoader classLoader = TwowaySecureStreamingTest.class.getClassLoader();
+ InputStream trustStore = classLoader.getResourceAsStream("client.truststore.pem");
+ InputStream keyStore = classLoader.getResourceAsStream("client.keystore.pem");
+ InputStream key = classLoader.getResourceAsStream("client.key.pem");
+ ChannelCredentials creds = TlsChannelCredentials.newBuilder()
+ .trustManager(trustStore)
+ .keyManager(keyStore, key)
+ .build();
+ channel = Grpc.newChannelBuilderForAddress(TARGET_HOST, TARGET_PORT, creds).build();
+ stub = ChatServiceGrpc.newStub(channel);
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/utility/ServerReload.java b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/utility/ServerReload.java
new file mode 100644
index 0000000..4cc0ffb
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/java/org/wildfly/feature/pack/grpc/test/utility/ServerReload.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2022 Red Hat
+ *
+ * Licensed 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
+ *
+ * https://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.wildfly.feature.pack.grpc.test.utility;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
+import org.jboss.as.controller.client.ModelControllerClient;
+import org.jboss.as.controller.client.ModelControllerClientConfiguration;
+import org.jboss.as.controller.client.helpers.Operations;
+import org.jboss.dmr.ModelNode;
+import org.junit.Assert;
+
+/**
+ * Utilities for handling server reloads.
+ *
+ * @author Stuart Douglas
+ * @author James R. Perkins
+ */
+@SuppressWarnings("unused")
+public class ServerReload {
+ /**
+ * Default time, in ms, to wait for reload to complete.
+ */
+ public static final int TIMEOUT = 100000;
+ private static final ModelNode EMPTY_ADDRESS = new ModelNode().setEmptyList();
+
+ /**
+ * Reloads the server and returns immediately.
+ *
+ * @param client the client used to execute the reload operation
+ */
+ public static void executeReload(final ModelControllerClient client) {
+ executeReload(client, Operations.createOperation("reload"));
+ }
+
+ /**
+ * Reloads the server and returns immediately.
+ *
+ * @param client the client used to execute the reload operation
+ * @param reloadOp the reload operation to execute
+ */
+ public static void executeReload(final ModelControllerClient client, final ModelNode reloadOp) {
+ try {
+ final ModelNode result = client.execute(reloadOp);
+ if (!Operations.isSuccessfulOutcome(result)) {
+ Assert.fail(Operations.getFailureDescription(result)
+ .asString());
+ }
+ } catch (IOException e) {
+ final Throwable cause = e.getCause();
+ if (!(cause instanceof ExecutionException) && !(cause instanceof CancellationException)) {
+ throw new RuntimeException(e);
+ } // else ignore, this might happen if the channel gets closed before we got the response
+ }
+ }
+
+ /**
+ * Executes a {@code reload} operation and waits the {@link #TIMEOUT default timeout}
+ * for the reload to complete.
+ *
+ * @param client the client to use for the request. Cannot be {@code null}
+ *
+ * @throws AssertionError if the reload does not complete within the timeout
+ */
+ public static void executeReloadAndWaitForCompletion(final ModelControllerClient client) {
+ executeReloadAndWaitForCompletion(client, TIMEOUT);
+ }
+
+ /**
+ * Executes a {@code reload} operation and waits a configurable maximum time for the reload to complete.
+ *
+ * @param client the client to use for the request. Cannot be {@code null}
+ * @param timeout maximum time to wait for the reload to complete, in milliseconds
+ *
+ * @throws AssertionError if the reload does not complete within the specified timeout
+ */
+ public static void executeReloadAndWaitForCompletion(final ModelControllerClient client, final int timeout) {
+ executeReload(client);
+ waitForLiveServerToReload(timeout, createDefaultConfig());
+ }
+
+ static ModelControllerClientConfiguration createDefaultConfig() {
+ return new ModelControllerClientConfiguration.Builder()
+ .setHostName("localhost")
+ .setPort(9990)
+ .build();
+ }
+
+ /**
+ * Returns the current running state, {@code server-state}, of the server.
+ *
+ * @param client the client used to execute the operation
+ *
+ * @return the running state or "failed" if the operation was unsuccessful
+ *
+ * @throws IOException if a communication error occurs
+ */
+ public static String getContainerRunningState(final ModelControllerClient client) throws IOException {
+ final ModelNode rsp = client.execute(Operations.createReadAttributeOperation(EMPTY_ADDRESS, "server-state"));
+ return Operations.isSuccessfulOutcome(rsp) ? Operations.readResult(rsp)
+ .asString() : "failed";
+ }
+
+ /**
+ * Checks if the container status is "reload-required" and if it's the case executes reload and waits for completion.
+ *
+ * @param client the client used to execute the operation
+ *
+ * @throws IOException if a communication error occurs
+ */
+ public static void reloadIfRequired(final ModelControllerClient client) throws Exception {
+ final String runningState = getContainerRunningState(client);
+ if ("reload-required".equalsIgnoreCase(runningState)) {
+ executeReloadAndWaitForCompletion(client);
+ } else {
+ Assert.assertEquals("running", runningState, "Server state 'running' is expected");
+ }
+ }
+
+ private static void waitForLiveServerToReload(final int timeout, final ModelControllerClientConfiguration config) {
+ final long start = System.currentTimeMillis();
+ final ModelNode operation = Operations.createReadAttributeOperation(EMPTY_ADDRESS, "server-state");
+ while (System.currentTimeMillis() - start < timeout) {
+ // do the sleep before we check, as the attribute state may not change instantly
+ // also reload generally takes longer than 100ms anyway
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignore) {
+ }
+ try (
+ ModelControllerClient liveClient = ModelControllerClient.Factory.create(config)) {
+ try {
+ final ModelNode result = liveClient.execute(operation);
+ if (Operations.isSuccessfulOutcome(result) && "running".equals(Operations.readResult(result)
+ .asString())) {
+ return;
+ }
+ } catch (IOException ignore) {
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ Assert.fail("Live Server did not reload in the imparted time.");
+ }
+}
diff --git a/testsuite/integration/subsystem/src/test/resources/arquillian2.xml b/testsuite/integration/subsystem/src/test/resources/arquillian2.xml
new file mode 100644
index 0000000..247376f
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/resources/arquillian2.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ target/wildfly
+ -Xmx512m -XX:MaxPermSize=128m -Xverify:none -XX:+UseFastAccessorMethods
+ -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y
+
+
+
+
+
\ No newline at end of file
diff --git a/testsuite/integration/subsystem/src/test/resources/arquillian3.xml b/testsuite/integration/subsystem/src/test/resources/arquillian3.xml
new file mode 100644
index 0000000..947c102
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/resources/arquillian3.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ target/wildfly
+ 9990
+
+
+ target/wildfly
+ true
+ 9990
+ -Xmx512m -XX:MaxPermSize=128m
+ -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y
+
+
+
+
+
\ No newline at end of file
diff --git a/testsuite/integration/subsystem/src/test/resources/arquillian_save.xml b/testsuite/integration/subsystem/src/test/resources/arquillian_save.xml
new file mode 100644
index 0000000..ae2ae9c
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/resources/arquillian_save.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ target/wildfly
+
+
+
\ No newline at end of file
diff --git a/testsuite/integration/subsystem/src/test/resources/client.cer b/testsuite/integration/subsystem/src/test/resources/client.cer
new file mode 100644
index 0000000..9ae2fa9
Binary files /dev/null and b/testsuite/integration/subsystem/src/test/resources/client.cer differ
diff --git a/testsuite/integration/subsystem/src/test/resources/client.key.pem b/testsuite/integration/subsystem/src/test/resources/client.key.pem
new file mode 100644
index 0000000..fa2f900
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/resources/client.key.pem
@@ -0,0 +1,20 @@
+Bag Attributes
+ friendlyName: client
+ localKeyID: 54 69 6D 65 20 31 36 37 33 36 35 37 30 32 32 31 31 34
+Key Attributes:
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMqFZbw9peJumKJM
+VkleSABBWMS2R2zmRhIGJTT4xbeMAVysdndArJ2m5/GDEI19HMc02TrNyXq8khkK
+toocPy2jRLwO70tI8zAt3YTaQXgbNPyESpLDVfCz1c6/06RujMeg1/n5p7YhtlHv
+Nr8KuZlsBCy/4FbYe0WrCy2BkSwdAgMBAAECgYBu8/CN1fSI/nCPEmV/orCtux9n
+/jlZdztSap19zQF9kq24WSA6K2umn6eZUGYELlRS6yhMKCxHGwKrx4vEVL9jRWAe
+SY6xf3pr4tFFs1XpuHIuVc+H9YMQR9MedXC3JLeC7EvPfQg0DWAAJtKl9hCEFohi
+sR2qkoZ16pbvL/1rwQJBAP8lQJzhHLJOTYjFSoNOz/oK/xCPknFuE7ecgAbOaaEJ
+vk66eB9NCoUn8ujvOUAgIOsvTx7QmOu9oyhziJLgAlECQQDLMwcaCV5vqIE2dzAL
+/JEj3fGdz++09//uCE8C1dcNUe2UYCUi8jdh61PRSowYp47w1tp3jj5Gvdh2QGPL
+Pq4NAkEAgEuBE+F2BoqtHgrmjuRAW+DPpMosvvC05WzCS6nbH2jA0uGcqVCZ657M
+3Cf+R6pgIyJkzH/jhRaURjDiCciuIQJAOFyFAcHLgekZPgQ9PXXmxC4RkJZWhLmt
+MVb4o26w4a7x2Q/5/QF2PyDI9OpahZQkX8UYf8TinTiXS+V8SKmwGQJBAJxg+hyK
+fZPzC/WzlkJqHGXUvcPpOcBc6qs5boq78UG/u5G4AJvUrFSSZ9IL0bsY8C1o1TS4
+ecvkJ50LfLdYYXM=
+-----END PRIVATE KEY-----
diff --git a/testsuite/integration/subsystem/src/test/resources/client.keystore.jks b/testsuite/integration/subsystem/src/test/resources/client.keystore.jks
new file mode 100644
index 0000000..f5e1a0a
Binary files /dev/null and b/testsuite/integration/subsystem/src/test/resources/client.keystore.jks differ
diff --git a/testsuite/integration/subsystem/src/test/resources/client.keystore.p12 b/testsuite/integration/subsystem/src/test/resources/client.keystore.p12
new file mode 100644
index 0000000..6ab0c23
Binary files /dev/null and b/testsuite/integration/subsystem/src/test/resources/client.keystore.p12 differ
diff --git a/testsuite/integration/subsystem/src/test/resources/client.keystore.pem b/testsuite/integration/subsystem/src/test/resources/client.keystore.pem
new file mode 100644
index 0000000..4e32712
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/resources/client.keystore.pem
@@ -0,0 +1,39 @@
+Bag Attributes
+ friendlyName: client
+ localKeyID: 54 69 6D 65 20 31 36 37 33 36 35 37 30 32 32 31 31 34
+Key Attributes:
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIC3TBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIY/0fxTlvaBsCAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAvft7Ihc6jzZ+wh6zw7l9IBIIC
+gAfZNHJCJjcYa6TAUPfDeJI52y2EiNAP2sXv1eVgjjYhjiBMGXouucUyGlhcHv93
+v6onflQcR104JH7gDxRH5h+irv5OUWxC6BnREBeYn5oCAAFLHFiw1wfo8V24eILV
+8GOFg/Bre4rjkH4EztCgvy967IXqLvkUWcB1RvMRb/cpiT3L1y6V85WE5nLdk84g
+PIwl2lVBeWlyPviTo1DHdB+PCgGBaWHDHar7CXhUmZGSPACeHXi8162De6z2fk+p
+IwvcK3REIPat0uOVVseKGSMosncG1h8EDOsFF0zvjfFQ70Yc/cFzeGlgkoYX/u8m
+JV7sW1X2e1cn9HsjF1mYmiwnNQVLRNeJv8NshzQAO3ub45lNEa2v69SaGLIbJezo
+DEa5IS+84PI9UBF97/jj2XO0ab8cfwTecgtOT0q0WyQWFoX/h2+7ZOF89mrerVcr
+KLvO8C0gcwN1LcglWhiNHFc0ouhWhBHRyUK0h3jFCThju2b6nIKETPIjrixjJqei
+EnID1GQdZwPPvi2sdgZam1owjWH/d+U7TU32Ur5NJ6+9HZRFbzGUBvi3bcbXkF49
+DOryeaMnmaIbEkqEeL/OqOII8eC5wUyKudbIO9QJRgZD4mO8lKg2ZW/fTz3KVnUf
+CtVWZnwQg3tqtnliHfrwcWuBfg6Jwf49a19R1k7sQl2q9y/nkmbLGDjd2evgYr4i
+kLNYzKhWIv3clA0wZZJj1Syz1ykKVbwTyb37Cj9hUGoGeWGqO3XijI12TgrjzKUj
+ksE6tPgfq4wjTYSUMlwm4V+1GYQ4CI541IQ00zfwSxycbYsAEPSuKcJeJ9Nk8oFj
+8r4CPLGgwDJPKxHXCutiP8w=
+-----END ENCRYPTED PRIVATE KEY-----
+Bag Attributes
+ friendlyName: client
+ localKeyID: 54 69 6D 65 20 31 36 37 33 36 35 37 30 32 32 31 31 34
+subject=CN = client
+issuer=CN = client
+-----BEGIN CERTIFICATE-----
+MIIBvDCCASWgAwIBAgIEJpQg0zANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDEwZj
+bGllbnQwHhcNMjMwMTE0MDA0MTQ5WhcNMjQwMTE0MDA0MTQ5WjARMQ8wDQYDVQQD
+EwZjbGllbnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMqFZbw9peJumKJM
+VkleSABBWMS2R2zmRhIGJTT4xbeMAVysdndArJ2m5/GDEI19HMc02TrNyXq8khkK
+toocPy2jRLwO70tI8zAt3YTaQXgbNPyESpLDVfCz1c6/06RujMeg1/n5p7YhtlHv
+Nr8KuZlsBCy/4FbYe0WrCy2BkSwdAgMBAAGjITAfMB0GA1UdDgQWBBROY4mupjhr
+cqPboXYZC2FI+L9/jzANBgkqhkiG9w0BAQsFAAOBgQC9xX7ODNZq17GMl2TRJ+fv
+QcQ4Wh+5CtzmSsCPgMClVZsMgWrp8Ksc3arvx0ePu9cTwTq7HlC1c6O13ix0+Oxr
+Ahv53obJHvhq1Nk+2PP36xp78MmpXnLZayoeL+aFxy43IppUWU9Vfzy/FsSrhLi5
+hLtBnft9OjYclipah6FR3w==
+-----END CERTIFICATE-----
diff --git a/testsuite/integration/subsystem/src/test/resources/client.truststore.jks b/testsuite/integration/subsystem/src/test/resources/client.truststore.jks
new file mode 100644
index 0000000..96e20a6
Binary files /dev/null and b/testsuite/integration/subsystem/src/test/resources/client.truststore.jks differ
diff --git a/testsuite/integration/subsystem/src/test/resources/client.truststore.p12 b/testsuite/integration/subsystem/src/test/resources/client.truststore.p12
new file mode 100644
index 0000000..36656c7
Binary files /dev/null and b/testsuite/integration/subsystem/src/test/resources/client.truststore.p12 differ
diff --git a/testsuite/integration/subsystem/src/test/resources/client.truststore.pem b/testsuite/integration/subsystem/src/test/resources/client.truststore.pem
new file mode 100644
index 0000000..1f154da
--- /dev/null
+++ b/testsuite/integration/subsystem/src/test/resources/client.truststore.pem
@@ -0,0 +1,26 @@
+Bag Attributes
+ friendlyName: localhost
+ 2.16.840.1.113894.746875.1.1:
+subject=C = Unknown, ST = Unknown, L = Unknown, O = Unknown, OU = Unknown, CN = localhost
+issuer=C = Unknown, ST = Unknown, L = Unknown, O = Unknown, OU = Unknown, CN = localhost
+-----BEGIN CERTIFICATE-----
+MIIDgDCCAmqgAwIBAgIJAM9zpAP+LEDUMAsGCSqGSIb3DQEBCzBuMRAwDgYDVQQG
+EwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAw
+DgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRIwEAYDVQQDEwlsb2Nh
+bGhvc3QwIhgPMjAyMzAxMTQwMDQ2NDZaGA8yMDIzMDQxMzIzNDY0NlowbjEQMA4G
+A1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93
+bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjESMBAGA1UEAxMJ
+bG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqv62Z24N
+VKAsPAOJEN+5rnVgIR97FGvJI+QBW8/+N6jcx1K1PS0XI2wMimQUtjFb3J7zoMYw
+krdMhfqtU/cKm+ZTzJdIrxnIW71HrcMv22Xdwl5O/j5RvoiEq3cwvlGyEHYFqfu/
+mLeZm1kHXp5agJaAzlPgGaRQf79XD3sPw9XgU1XdrfNOVQjjVAEsOcBisHg+IHgl
+NPaVt7bZc/3obpLtoXfCae5WTF7Cf4asQXatgCgmGee98IjYhFX1YcHIFEB1J090
+Qa933jBhxDaL80ry1T0lqkF8m1imgluXvCwTasD6rM16GHSQfyYyaDlwZvm4z8cP
+L9BP6T/YDsZuFQIDAQABoyEwHzAdBgNVHQ4EFgQUwR9o9yHBtox/AdxGjxyoKwXw
+8TowCwYJKoZIhvcNAQELA4IBAQBYlRNkZfTJJpXGlCVbh3comEEinb6PNXupe3AB
+ra3rsUGkWRR5NXp3Lu7M+0GQCz38etvV0sIEnH246M0igyYgbL1tS5O9pWfPeS77
+YDzeX4A9Vn1nYrKn70QgwazNDSp6BU7jckHZ5UiqkUmFHCnmB1vcvVy8ugFbHJdX
+cNZ+3xyqKV5j+HZO8r/pFYPkRLwroZWf62wA9VY0Pf3ldnYtXzQ2iXqd0N/M+fMz
+COC894eZ/pjKoyIoLGpmonw+B5jBAWAEFrDXkaYKGyqRqbPK374RiM8a2KvVzjlV
+1aj1WptImi60JW0Pp79fk/Z6vrcSeTtWWHGEPqP+CzxR2AkS
+-----END CERTIFICATE-----