Skip to content

Commit 915c706

Browse files
kenk42292ejona86
authored andcommitted
android: Add UDSChannelBuilder
Allows using Android's LocalSocket via a Socket adapter. Such an adapter isn't generally 100% safe, since some methods may not have any effect, but we know what methods are called by gRPC's okhttp transport and can update the adapter or the transport as appropriate.
1 parent cc03b48 commit 915c706

File tree

10 files changed

+897
-6
lines changed

10 files changed

+897
-6
lines changed

android-interop-testing/build.gradle

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ android {
5252
disable 'InvalidPackage', 'HardcodedText', 'UsingOnClickInXml',
5353
'MissingClass' // https://github.com/grpc/grpc-java/issues/8799
5454
}
55+
packagingOptions {
56+
exclude 'META-INF/INDEX.LIST'
57+
exclude 'META-INF/io.netty.versions.properties'
58+
}
5559
}
5660

5761
dependencies {
@@ -60,7 +64,8 @@ dependencies {
6064
implementation libraries.androidx.annotation
6165
implementation 'com.google.android.gms:play-services-base:18.0.1'
6266

63-
implementation project(':grpc-auth'),
67+
implementation project(':grpc-android'),
68+
project(':grpc-auth'),
6469
project(':grpc-census'),
6570
project(':grpc-okhttp'),
6671
project(':grpc-protobuf-lite'),
@@ -69,6 +74,7 @@ dependencies {
6974
libraries.hdrhistogram,
7075
libraries.junit,
7176
libraries.truth,
77+
libraries.androidx.test.rules,
7278
libraries.opencensus.contrib.grpc.metrics
7379

7480
implementation (libraries.google.auth.oauth2Http) {
@@ -81,7 +87,8 @@ dependencies {
8187

8288
compileOnly libraries.javax.annotation
8389

84-
androidTestImplementation 'androidx.test.ext:junit:1.1.3',
90+
androidTestImplementation project(':grpc-netty'),
91+
'androidx.test.ext:junit:1.1.3',
8592
'androidx.test:runner:1.4.0'
8693
}
8794

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright 2021 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.android.integrationtest;
18+
19+
import static java.util.concurrent.TimeUnit.SECONDS;
20+
import static org.junit.Assert.assertEquals;
21+
22+
import android.net.LocalSocketAddress.Namespace;
23+
import androidx.test.InstrumentationRegistry;
24+
import androidx.test.rule.ActivityTestRule;
25+
import androidx.test.runner.AndroidJUnit4;
26+
import com.google.common.util.concurrent.SettableFuture;
27+
import io.grpc.Server;
28+
import io.grpc.android.UdsChannelBuilder;
29+
import io.grpc.android.integrationtest.InteropTask.Listener;
30+
import io.grpc.netty.NettyServerBuilder;
31+
import io.grpc.testing.integration.TestServiceImpl;
32+
import java.io.IOException;
33+
import java.util.concurrent.Executors;
34+
import java.util.concurrent.ScheduledExecutorService;
35+
import org.junit.After;
36+
import org.junit.Before;
37+
import org.junit.Rule;
38+
import org.junit.Test;
39+
import org.junit.runner.RunWith;
40+
41+
/**
42+
* Tests for channels created with {@link UdsChannelBuilder}. The UDS Channel is only meant to talk
43+
* to Unix Domain Socket endpoints on servers that are on-device, so a {@link LocalTestServer} is
44+
* set up to expose a UDS endpoint.
45+
*/
46+
@RunWith(AndroidJUnit4.class)
47+
public class UdsChannelInteropTest {
48+
private static final int TIMEOUT_SECONDS = 150;
49+
50+
private static final String UDS_PATH = "udspath";
51+
private String testCase;
52+
53+
private Server server;
54+
private UdsTcpEndpointConnector endpointConnector;
55+
56+
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
57+
58+
// Ensures Looper is initialized for tests running on API level 15. Otherwise instantiating an
59+
// AsyncTask throws an exception.
60+
@Rule
61+
public ActivityTestRule<TesterActivity> activityRule =
62+
new ActivityTestRule<TesterActivity>(TesterActivity.class);
63+
64+
@Before
65+
public void setUp() throws IOException {
66+
testCase = InstrumentationRegistry.getArguments().getString("test_case", "all");
67+
68+
// Start local server.
69+
server =
70+
NettyServerBuilder.forPort(0)
71+
.maxInboundMessageSize(16 * 1024 * 1024)
72+
.addService(new TestServiceImpl(executor))
73+
.build();
74+
server.start();
75+
76+
// Connect uds endpoint to server's endpoint.
77+
endpointConnector = new UdsTcpEndpointConnector(UDS_PATH, "0.0.0.0", server.getPort());
78+
endpointConnector.start();
79+
}
80+
81+
@After
82+
public void teardown() {
83+
server.shutdownNow();
84+
endpointConnector.shutDown();
85+
}
86+
87+
@Test
88+
public void interopTests() throws Exception {
89+
if (testCase.equals("all")) {
90+
runTest("empty_unary");
91+
runTest("large_unary");
92+
runTest("client_streaming");
93+
runTest("server_streaming");
94+
runTest("ping_pong");
95+
runTest("empty_stream");
96+
runTest("cancel_after_begin");
97+
runTest("cancel_after_first_response");
98+
runTest("full_duplex_call_should_succeed");
99+
runTest("half_duplex_call_should_succeed");
100+
runTest("server_streaming_should_be_flow_controlled");
101+
runTest("very_large_request");
102+
runTest("very_large_response");
103+
runTest("deadline_not_exceeded");
104+
runTest("deadline_exceeded");
105+
runTest("deadline_exceeded_server_streaming");
106+
runTest("unimplemented_method");
107+
runTest("timeout_on_sleeping_server");
108+
runTest("graceful_shutdown");
109+
} else {
110+
runTest(testCase);
111+
}
112+
}
113+
114+
private void runTest(String testCase) throws Exception {
115+
final SettableFuture<String> resultFuture = SettableFuture.create();
116+
InteropTask.Listener listener =
117+
new Listener() {
118+
@Override
119+
public void onComplete(String result) {
120+
resultFuture.set(result);
121+
}
122+
};
123+
124+
new InteropTask(
125+
listener,
126+
UdsChannelBuilder.forPath(UDS_PATH, Namespace.ABSTRACT)
127+
.maxInboundMessageSize(16 * 1024 * 1024)
128+
.build(),
129+
testCase)
130+
.execute();
131+
String result = resultFuture.get(TIMEOUT_SECONDS, SECONDS);
132+
assertEquals(testCase + " failed", InteropTask.SUCCESS_MESSAGE, result);
133+
}
134+
}

android-interop-testing/src/main/AndroidManifest.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
33

44
<uses-permission android:name="android.permission.INTERNET" />
5+
<!-- For UDS -->
6+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
7+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
58

69
<application
710
android:allowBackup="true"
811
android:icon="@mipmap/ic_launcher"
912
android:label="@string/app_name"
1013
android:theme="@style/Base.V7.Theme.AppCompat.Light"
11-
android:name="androidx.multidex.MultiDexApplication" >
14+
android:name="androidx.multidex.MultiDexApplication">
1215
<activity
1316
android:name=".TesterActivity"
1417
android:exported="true"

android-interop-testing/src/main/java/io/grpc/android/integrationtest/TesterActivity.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import android.content.Context;
2020
import android.content.Intent;
21+
import android.net.LocalSocketAddress.Namespace;
2122
import android.os.Bundle;
2223
import android.text.TextUtils;
2324
import android.util.Log;
@@ -30,6 +31,8 @@
3031
import androidx.appcompat.app.AppCompatActivity;
3132
import com.google.android.gms.security.ProviderInstaller;
3233
import io.grpc.ManagedChannel;
34+
import io.grpc.android.UdsChannelBuilder;
35+
import java.io.IOException;
3336
import java.io.InputStream;
3437
import java.util.ArrayList;
3538
import java.util.List;
@@ -41,9 +44,13 @@ public class TesterActivity extends AppCompatActivity
4144
private List<Button> buttons;
4245
private EditText hostEdit;
4346
private EditText portEdit;
47+
private CheckBox useUdsCheckBox;
48+
private EditText udsEdit;
4449
private TextView resultText;
4550
private CheckBox testCertCheckBox;
4651

52+
private UdsTcpEndpointConnector endpointConnector;
53+
4754
@Override
4855
protected void onCreate(Bundle savedInstanceState) {
4956
super.onCreate(savedInstanceState);
@@ -57,6 +64,8 @@ protected void onCreate(Bundle savedInstanceState) {
5764

5865
hostEdit = (EditText) findViewById(R.id.host_edit_text);
5966
portEdit = (EditText) findViewById(R.id.port_edit_text);
67+
useUdsCheckBox = (CheckBox) findViewById(R.id.use_uds_checkbox);
68+
udsEdit = (EditText) findViewById(R.id.uds_proxy_edit_text);
6069
resultText = (TextView) findViewById(R.id.grpc_response_text);
6170
testCertCheckBox = (CheckBox) findViewById(R.id.test_cert_checkbox);
6271

@@ -65,6 +74,16 @@ protected void onCreate(Bundle savedInstanceState) {
6574
enableButtons(false);
6675
}
6776

77+
/** Click handler for unix domain socket. */
78+
public void enableUds(View view) {
79+
boolean enabled = ((CheckBox) view).isChecked();
80+
udsEdit.setEnabled(enabled);
81+
testCertCheckBox.setEnabled(!enabled);
82+
if (enabled) {
83+
testCertCheckBox.setChecked(false);
84+
}
85+
}
86+
6887
public void startEmptyUnary(View view) {
6988
startTest("empty_unary");
7089
}
@@ -93,6 +112,10 @@ private void enableButtons(boolean enable) {
93112

94113
@Override
95114
public void onComplete(String result) {
115+
if (endpointConnector != null) {
116+
endpointConnector.shutDown();
117+
endpointConnector = null;
118+
}
96119
resultText.setText(result);
97120
enableButtons(true);
98121
}
@@ -106,6 +129,9 @@ private void startTest(String testCase) {
106129
String host = hostEdit.getText().toString();
107130
String portStr = portEdit.getText().toString();
108131
int port = TextUtils.isEmpty(portStr) ? 8080 : Integer.valueOf(portStr);
132+
boolean udsEnabled = useUdsCheckBox.isChecked();
133+
String udsPath =
134+
TextUtils.isEmpty(udsEdit.getText()) ? "default" : udsEdit.getText().toString();
109135

110136
String serverHostOverride;
111137
InputStream testCert;
@@ -116,10 +142,27 @@ private void startTest(String testCase) {
116142
serverHostOverride = null;
117143
testCert = null;
118144
}
119-
ManagedChannel channel =
120-
TesterOkHttpChannelBuilder.build(host, port, serverHostOverride, true, testCert);
121145

122-
new InteropTask(this, channel, testCase).execute();
146+
// Create Channel
147+
ManagedChannel channel;
148+
if (udsEnabled) {
149+
channel = UdsChannelBuilder.forPath(udsPath, Namespace.ABSTRACT).build();
150+
} else {
151+
channel = TesterOkHttpChannelBuilder.build(host, port, serverHostOverride, true, testCert);
152+
}
153+
154+
// Port-forward uds local port to server exposing tcp endpoint.
155+
if (udsEnabled) {
156+
endpointConnector = new UdsTcpEndpointConnector(udsPath, host, port);
157+
try {
158+
endpointConnector.start();
159+
} catch (IOException e) {
160+
Log.e(LOG_TAG, "Failed to start UDS-TCP Endpoint Connector.");
161+
}
162+
}
163+
164+
// Start Test.
165+
new InteropTask(TesterActivity.this, channel, testCase).execute();
123166
}
124167

125168
@Override

0 commit comments

Comments
 (0)