Merge "adopt non-blocking method to obtain the IpMemoryStore service."

This commit is contained in:
Xiao Ma
2019-05-09 11:30:56 +00:00
committed by Gerrit Code Review
5 changed files with 357 additions and 73 deletions

View File

@@ -19,6 +19,9 @@ package android.net;
import android.annotation.NonNull;
import android.content.Context;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
/**
* service used to communicate with the ip memory store service in network stack,
* which is running in the same module.
@@ -35,8 +38,7 @@ public class NetworkStackIpMemoryStore extends IpMemoryStoreClient {
}
@Override
@NonNull
protected IIpMemoryStore getService() {
return mService;
protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException {
cb.accept(mService);
}
}

View File

@@ -18,11 +18,14 @@ package android.net;
import android.annotation.NonNull;
import android.content.Context;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
* Manager class used to communicate with the ip memory store service in the network stack,
@@ -30,15 +33,18 @@ import java.util.concurrent.ExecutionException;
* @hide
*/
public class IpMemoryStore extends IpMemoryStoreClient {
private final CompletableFuture<IIpMemoryStore> mService;
private static final String TAG = IpMemoryStore.class.getSimpleName();
@NonNull private final CompletableFuture<IIpMemoryStore> mService;
@NonNull private final AtomicReference<CompletableFuture<IIpMemoryStore>> mTailNode;
public IpMemoryStore(@NonNull final Context context) {
super(context);
mService = new CompletableFuture<>();
mTailNode = new AtomicReference<CompletableFuture<IIpMemoryStore>>(mService);
getNetworkStackClient().fetchIpMemoryStore(
new IIpMemoryStoreCallbacks.Stub() {
@Override
public void onIpMemoryStoreFetched(final IIpMemoryStore memoryStore) {
public void onIpMemoryStoreFetched(@NonNull final IIpMemoryStore memoryStore) {
mService.complete(memoryStore);
}
@@ -49,9 +55,28 @@ public class IpMemoryStore extends IpMemoryStoreClient {
});
}
/*
* If the IpMemoryStore is ready, this function will run the request synchronously.
* Otherwise, it will enqueue the requests for execution immediately after the
* service becomes ready. The requests are guaranteed to be executed in the order
* they are sumbitted.
*/
@Override
protected IIpMemoryStore getService() throws InterruptedException, ExecutionException {
return mService.get();
protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException {
mTailNode.getAndUpdate(future -> future.handle((store, exception) -> {
if (exception != null) {
// this should never happens since we also catch the exception below
Log.wtf(TAG, "Error fetching IpMemoryStore", exception);
return store;
}
try {
cb.accept(store);
} catch (Exception e) {
Log.wtf(TAG, "Exception occured: " + e.getMessage());
}
return store;
}));
}
@VisibleForTesting

View File

@@ -31,6 +31,7 @@ import android.os.RemoteException;
import android.util.Log;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
/**
* service used to communicate with the ip memory store service in network stack,
@@ -46,8 +47,25 @@ public abstract class IpMemoryStoreClient {
mContext = context;
}
@NonNull
protected abstract IIpMemoryStore getService() throws InterruptedException, ExecutionException;
protected abstract void runWhenServiceReady(Consumer<IIpMemoryStore> cb)
throws ExecutionException;
@FunctionalInterface
private interface ThrowingRunnable {
void run() throws RemoteException;
}
private void ignoringRemoteException(ThrowingRunnable r) {
ignoringRemoteException("Failed to execute remote procedure call", r);
}
private void ignoringRemoteException(String message, ThrowingRunnable r) {
try {
r.run();
} catch (RemoteException e) {
Log.e(TAG, message, e);
}
}
/**
* Store network attributes for a given L2 key.
@@ -69,14 +87,12 @@ public abstract class IpMemoryStoreClient {
@NonNull final NetworkAttributes attributes,
@Nullable final OnStatusListener listener) {
try {
try {
getService().storeNetworkAttributes(l2Key, attributes.toParcelable(),
OnStatusListener.toAIDL(listener));
} catch (InterruptedException | ExecutionException m) {
listener.onComplete(new Status(Status.ERROR_UNKNOWN));
}
} catch (RemoteException e) {
Log.e(TAG, "Error storing network attributes", e);
runWhenServiceReady(service -> ignoringRemoteException(
() -> service.storeNetworkAttributes(l2Key, attributes.toParcelable(),
OnStatusListener.toAIDL(listener))));
} catch (ExecutionException m) {
ignoringRemoteException("Error storing network attributes",
() -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
}
}
@@ -95,14 +111,12 @@ public abstract class IpMemoryStoreClient {
@NonNull final String name, @NonNull final Blob data,
@Nullable final OnStatusListener listener) {
try {
try {
getService().storeBlob(l2Key, clientId, name, data,
OnStatusListener.toAIDL(listener));
} catch (InterruptedException | ExecutionException m) {
listener.onComplete(new Status(Status.ERROR_UNKNOWN));
}
} catch (RemoteException e) {
Log.e(TAG, "Error storing blob", e);
runWhenServiceReady(service -> ignoringRemoteException(
() -> service.storeBlob(l2Key, clientId, name, data,
OnStatusListener.toAIDL(listener))));
} catch (ExecutionException m) {
ignoringRemoteException("Error storing blob",
() -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
}
}
@@ -123,14 +137,12 @@ public abstract class IpMemoryStoreClient {
public void findL2Key(@NonNull final NetworkAttributes attributes,
@NonNull final OnL2KeyResponseListener listener) {
try {
try {
getService().findL2Key(attributes.toParcelable(),
OnL2KeyResponseListener.toAIDL(listener));
} catch (InterruptedException | ExecutionException m) {
listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null);
}
} catch (RemoteException e) {
Log.e(TAG, "Error finding L2 Key", e);
runWhenServiceReady(service -> ignoringRemoteException(
() -> service.findL2Key(attributes.toParcelable(),
OnL2KeyResponseListener.toAIDL(listener))));
} catch (ExecutionException m) {
ignoringRemoteException("Error finding L2 Key",
() -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null));
}
}
@@ -146,14 +158,12 @@ public abstract class IpMemoryStoreClient {
public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
@NonNull final OnSameL3NetworkResponseListener listener) {
try {
try {
getService().isSameNetwork(l2Key1, l2Key2,
OnSameL3NetworkResponseListener.toAIDL(listener));
} catch (InterruptedException | ExecutionException m) {
listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null);
}
} catch (RemoteException e) {
Log.e(TAG, "Error checking for network sameness", e);
runWhenServiceReady(service -> ignoringRemoteException(
() -> service.isSameNetwork(l2Key1, l2Key2,
OnSameL3NetworkResponseListener.toAIDL(listener))));
} catch (ExecutionException m) {
ignoringRemoteException("Error checking for network sameness",
() -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null));
}
}
@@ -169,14 +179,13 @@ public abstract class IpMemoryStoreClient {
public void retrieveNetworkAttributes(@NonNull final String l2Key,
@NonNull final OnNetworkAttributesRetrievedListener listener) {
try {
try {
getService().retrieveNetworkAttributes(l2Key,
OnNetworkAttributesRetrievedListener.toAIDL(listener));
} catch (InterruptedException | ExecutionException m) {
listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN), null, null);
}
} catch (RemoteException e) {
Log.e(TAG, "Error retrieving network attributes", e);
runWhenServiceReady(service -> ignoringRemoteException(
() -> service.retrieveNetworkAttributes(l2Key,
OnNetworkAttributesRetrievedListener.toAIDL(listener))));
} catch (ExecutionException m) {
ignoringRemoteException("Error retrieving network attributes",
() -> listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN),
null, null));
}
}
@@ -194,14 +203,13 @@ public abstract class IpMemoryStoreClient {
public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
@NonNull final String name, @NonNull final OnBlobRetrievedListener listener) {
try {
try {
getService().retrieveBlob(l2Key, clientId, name,
OnBlobRetrievedListener.toAIDL(listener));
} catch (InterruptedException | ExecutionException m) {
listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN), null, null, null);
}
} catch (RemoteException e) {
Log.e(TAG, "Error retrieving blob", e);
runWhenServiceReady(service -> ignoringRemoteException(
() -> service.retrieveBlob(l2Key, clientId, name,
OnBlobRetrievedListener.toAIDL(listener))));
} catch (ExecutionException m) {
ignoringRemoteException("Error retrieving blob",
() -> listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN),
null, null, null));
}
}
}

View File

@@ -16,10 +16,26 @@
package android.net;
import static org.mockito.ArgumentMatchers.any;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.ipmemorystore.Blob;
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
import android.net.ipmemorystore.Status;
import android.os.RemoteException;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -27,28 +43,57 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.net.UnknownHostException;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class IpMemoryStoreTest {
private static final String TAG = IpMemoryStoreTest.class.getSimpleName();
private static final String TEST_CLIENT_ID = "testClientId";
private static final String TEST_DATA_NAME = "testData";
private static final String TEST_OTHER_DATA_NAME = TEST_DATA_NAME + "Other";
private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
-128, 0, 89, 112, 91, -34 };
private static final NetworkAttributes TEST_NETWORK_ATTRIBUTES = buildTestNetworkAttributes(
"hint", 219);
@Mock
Context mMockContext;
@Mock
NetworkStackClient mNetworkStackClient;
@Mock
IIpMemoryStore mMockService;
@Mock
IOnStatusListener mIOnStatusListener;
IpMemoryStore mStore;
@Captor
ArgumentCaptor<IIpMemoryStoreCallbacks> mCbCaptor;
@Captor
ArgumentCaptor<NetworkAttributesParcelable> mNapCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doAnswer(invocation -> {
((IIpMemoryStoreCallbacks) invocation.getArgument(0))
.onIpMemoryStoreFetched(mMockService);
return null;
}).when(mNetworkStackClient).fetchIpMemoryStore(any());
}
private void startIpMemoryStore(boolean supplyService) {
if (supplyService) {
doAnswer(invocation -> {
((IIpMemoryStoreCallbacks) invocation.getArgument(0))
.onIpMemoryStoreFetched(mMockService);
return null;
}).when(mNetworkStackClient).fetchIpMemoryStore(any());
} else {
doNothing().when(mNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture());
}
mStore = new IpMemoryStore(mMockContext) {
@Override
protected NetworkStackClient getNetworkStackClient() {
@@ -57,24 +102,228 @@ public class IpMemoryStoreTest {
};
}
@Test
public void testNetworkAttributes() {
// TODO : implement this
private static NetworkAttributes buildTestNetworkAttributes(String hint, int mtu) {
return new NetworkAttributes.Builder()
.setGroupHint(hint)
.setMtu(mtu)
.build();
}
@Test
public void testPrivateData() {
// TODO : implement this
public void testNetworkAttributes() throws Exception {
startIpMemoryStore(true);
final String l2Key = "fakeKey";
mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
status -> {
assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
});
verify(mMockService, times(1)).storeNetworkAttributes(eq(l2Key),
mNapCaptor.capture(), any());
assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
mStore.retrieveNetworkAttributes(l2Key,
(status, key, attr) -> {
assertTrue("Retrieve network attributes not successful : "
+ status.resultCode, status.isSuccess());
assertEquals(l2Key, key);
assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
});
verify(mMockService, times(1)).retrieveNetworkAttributes(eq(l2Key), any());
}
@Test
public void testFindL2Key() {
// TODO : implement this
public void testPrivateData() throws RemoteException {
startIpMemoryStore(true);
final Blob b = new Blob();
b.data = TEST_BLOB_DATA;
final String l2Key = "fakeKey";
mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
status -> {
assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
});
verify(mMockService, times(1)).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
eq(b), any());
mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
(status, key, name, data) -> {
assertTrue("Retrieve blob status not successful : " + status.resultCode,
status.isSuccess());
assertEquals(l2Key, key);
assertEquals(name, TEST_DATA_NAME);
assertTrue(Arrays.equals(b.data, data.data));
});
verify(mMockService, times(1)).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
eq(TEST_OTHER_DATA_NAME), any());
}
@Test
public void testIsSameNetwork() {
// TODO : implement this
public void testFindL2Key()
throws UnknownHostException, RemoteException, Exception {
startIpMemoryStore(true);
final String l2Key = "fakeKey";
mStore.findL2Key(TEST_NETWORK_ATTRIBUTES,
(status, key) -> {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertEquals(l2Key, key);
});
verify(mMockService, times(1)).findL2Key(mNapCaptor.capture(), any());
assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
}
@Test
public void testIsSameNetwork() throws UnknownHostException, RemoteException {
startIpMemoryStore(true);
final String l2Key1 = "fakeKey1";
final String l2Key2 = "fakeKey2";
mStore.isSameNetwork(l2Key1, l2Key2,
(status, answer) -> {
assertFalse("Retrieve network sameness suspiciously successful : "
+ status.resultCode, status.isSuccess());
assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
assertNull(answer);
});
verify(mMockService, times(1)).isSameNetwork(eq(l2Key1), eq(l2Key2), any());
}
@Test
public void testEnqueuedIpMsRequests() throws Exception {
startIpMemoryStore(false);
final Blob b = new Blob();
b.data = TEST_BLOB_DATA;
final String l2Key = "fakeKey";
// enqueue multiple ipms requests
mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
status -> {
assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
});
mStore.retrieveNetworkAttributes(l2Key,
(status, key, attr) -> {
assertTrue("Retrieve network attributes not successful : "
+ status.resultCode, status.isSuccess());
assertEquals(l2Key, key);
assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
});
mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
status -> {
assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
});
mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
(status, key, name, data) -> {
assertTrue("Retrieve blob status not successful : " + status.resultCode,
status.isSuccess());
assertEquals(l2Key, key);
assertEquals(name, TEST_DATA_NAME);
assertTrue(Arrays.equals(b.data, data.data));
});
// get ipms service ready
mCbCaptor.getValue().onIpMemoryStoreFetched(mMockService);
InOrder inOrder = inOrder(mMockService);
inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any());
inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
eq(b), any());
inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
eq(TEST_OTHER_DATA_NAME), any());
assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
}
@Test
public void testEnqueuedIpMsRequestsWithException() throws Exception {
startIpMemoryStore(true);
doThrow(RemoteException.class).when(mMockService).retrieveNetworkAttributes(any(), any());
final Blob b = new Blob();
b.data = TEST_BLOB_DATA;
final String l2Key = "fakeKey";
// enqueue multiple ipms requests
mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
status -> {
assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
});
mStore.retrieveNetworkAttributes(l2Key,
(status, key, attr) -> {
assertTrue("Retrieve network attributes not successful : "
+ status.resultCode, status.isSuccess());
assertEquals(l2Key, key);
assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
});
mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
status -> {
assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
});
mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
(status, key, name, data) -> {
assertTrue("Retrieve blob status not successful : " + status.resultCode,
status.isSuccess());
assertEquals(l2Key, key);
assertEquals(name, TEST_DATA_NAME);
assertTrue(Arrays.equals(b.data, data.data));
});
// verify the rest of the queue is still processed in order even if the remote exception
// occurs when calling one or more requests
InOrder inOrder = inOrder(mMockService);
inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
eq(b), any());
inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
eq(TEST_OTHER_DATA_NAME), any());
assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
}
@Test
public void testEnqueuedIpMsRequestsCallbackFunctionWithException() throws Exception {
startIpMemoryStore(true);
final Blob b = new Blob();
b.data = TEST_BLOB_DATA;
final String l2Key = "fakeKey";
// enqueue multiple ipms requests
mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
status -> {
assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
});
mStore.retrieveNetworkAttributes(l2Key,
(status, key, attr) -> {
throw new RuntimeException("retrieveNetworkAttributes test");
});
mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
status -> {
throw new RuntimeException("storeBlob test");
});
mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
(status, key, name, data) -> {
assertTrue("Retrieve blob status not successful : " + status.resultCode,
status.isSuccess());
assertEquals(l2Key, key);
assertEquals(name, TEST_DATA_NAME);
assertTrue(Arrays.equals(b.data, data.data));
});
// verify the rest of the queue is still processed in order even if when one or more
// callback throw the remote exception
InOrder inOrder = inOrder(mMockService);
inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(),
any());
inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
eq(b), any());
inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
eq(TEST_OTHER_DATA_NAME), any());
assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.server.connectivity.ipmemorystore;
package com.android.server.net.ipmemorystore;
import static org.junit.Assert.assertEquals;