Reevaluate VcnGatewayConnections on receiving new configs.

This CL updates Vcn.java to reevaluate its VcnGatewayConnections when it
receives a new VcnConfig. This may result in VcnGatewayConnections being
torn down (if their VcnGatewayConnectionConfig is not in the updated
VcnConfig) or established (if a NetworkRequest matches a new
VcnGatewayConnectionConfig).

Bug: 181815405
Test: atest FrameworksVcnTests CtsVcnTestCases
Change-Id: I7da60f60e972bd968e1ff4fe416fb9a1d3309a18
This commit is contained in:
Cody Kesting
2021-03-09 17:10:26 -08:00
parent 79ddf1afb8
commit 3a51a7d152
2 changed files with 138 additions and 17 deletions

View File

@@ -41,6 +41,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -110,6 +111,24 @@ public class Vcn extends Handler {
@NonNull private final VcnNetworkRequestListener mRequestListener; @NonNull private final VcnNetworkRequestListener mRequestListener;
@NonNull private final VcnCallback mVcnCallback; @NonNull private final VcnCallback mVcnCallback;
/**
* Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
*
* <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added
* to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives
* a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig.
*
* <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise
* there is potential for a orphaned VcnGatewayConnection instance that does not get properly
* shut down.
*
* <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this
* map once they have finished tearing down, which is reported to this VCN via {@link
* VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from
* the NetworkProvider so that another VcnGatewayConnectionConfig can match the
* previously-matched request.
*/
// TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles
@NonNull @NonNull
private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections = private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
new HashMap<>(); new HashMap<>();
@@ -191,6 +210,19 @@ public class Vcn extends Handler {
return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values())); return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
} }
/** Get current Configs and Gateways for testing purposes */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public Map<VcnGatewayConnectionConfig, VcnGatewayConnection>
getVcnGatewayConnectionConfigMap() {
return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections));
}
/** Set whether this Vcn is active for testing purposes */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public void setIsActive(boolean isActive) {
mIsActive.set(isActive);
}
private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener { private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
@Override @Override
public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
@@ -202,11 +234,6 @@ public class Vcn extends Handler {
@Override @Override
public void handleMessage(@NonNull Message msg) { public void handleMessage(@NonNull Message msg) {
// Ignore if this Vcn is not active and we're not receiving new configs
if (!isActive() && msg.what != MSG_EVENT_CONFIG_UPDATED) {
return;
}
switch (msg.what) { switch (msg.what) {
case MSG_EVENT_CONFIG_UPDATED: case MSG_EVENT_CONFIG_UPDATED:
handleConfigUpdated((VcnConfig) msg.obj); handleConfigUpdated((VcnConfig) msg.obj);
@@ -237,9 +264,31 @@ public class Vcn extends Handler {
mConfig = config; mConfig = config;
// TODO(b/181815405): Reevaluate active VcnGatewayConnection(s) if (mIsActive.getAndSet(true)) {
// VCN is already active - teardown any GatewayConnections whose configs have been
// removed and get all current requests
for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
mVcnGatewayConnections.entrySet()) {
final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
final VcnGatewayConnection gatewayConnection = entry.getValue();
if (!mIsActive.getAndSet(true)) { // GatewayConnectionConfigs must match exactly (otherwise authentication or
// connection details may have changed).
if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
if (gatewayConnection == null) {
Slog.wtf(
getLogTag(),
"Found gatewayConnectionConfig without GatewayConnection");
} else {
gatewayConnection.teardownAsynchronously();
}
}
}
// Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
// satisfied start a new GatewayConnection)
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
} else {
// If this VCN was not previously active, it is exiting Safe Mode. Re-register the // If this VCN was not previously active, it is exiting Safe Mode. Re-register the
// request listener to get NetworkRequests again (and all cached requests). // request listener to get NetworkRequests again (and all cached requests).
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
@@ -259,13 +308,16 @@ public class Vcn extends Handler {
private void handleEnterSafeMode() { private void handleEnterSafeMode() {
handleTeardown(); handleTeardown();
mVcnGatewayConnections.clear();
mVcnCallback.onEnteredSafeMode(); mVcnCallback.onEnteredSafeMode();
} }
private void handleNetworkRequested( private void handleNetworkRequested(
@NonNull NetworkRequest request, int score, int providerId) { @NonNull NetworkRequest request, int score, int providerId) {
if (!isActive()) {
Slog.v(getLogTag(), "Received NetworkRequest while inactive. Ignore for now");
return;
}
if (score > getNetworkScore()) { if (score > getNetworkScore()) {
if (VDBG) { if (VDBG) {
Slog.v( Slog.v(
@@ -318,8 +370,10 @@ public class Vcn extends Handler {
mVcnGatewayConnections.remove(config); mVcnGatewayConnections.remove(config);
// Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
// start a new GatewayConnection) // start a new GatewayConnection), but only if the Vcn is still active
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); if (isActive()) {
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
}
} }
private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {

View File

@@ -29,6 +29,7 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -139,8 +140,7 @@ public class VcnTest {
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
} }
@Test private void verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(boolean isActive) {
public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener(); final NetworkRequestListener requestListener = verifyAndGetRequestListener();
startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]); startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]);
@@ -150,14 +150,27 @@ public class VcnTest {
final TelephonySubscriptionSnapshot updatedSnapshot = final TelephonySubscriptionSnapshot updatedSnapshot =
mock(TelephonySubscriptionSnapshot.class); mock(TelephonySubscriptionSnapshot.class);
mVcn.setIsActive(isActive);
mVcn.updateSubscriptionSnapshot(updatedSnapshot); mVcn.updateSubscriptionSnapshot(updatedSnapshot);
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
for (final VcnGatewayConnection gateway : gatewayConnections) { for (final VcnGatewayConnection gateway : gatewayConnections) {
verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot)); verify(gateway, isActive ? times(1) : never())
.updateSubscriptionSnapshot(eq(updatedSnapshot));
} }
} }
@Test
public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(true /* isActive */);
}
@Test
public void testSubscriptionSnapshotUpdatesVcnGatewayConnectionsWhileInactive() {
verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(false /* isActive */);
}
private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { private void triggerVcnRequestListeners(NetworkRequestListener requestListener) {
for (final int[] caps : TEST_CAPS) { for (final int[] caps : TEST_CAPS) {
startVcnGatewayWithCapabilities(requestListener, caps); startVcnGatewayWithCapabilities(requestListener, caps);
@@ -187,7 +200,6 @@ public class VcnTest {
NetworkRequestListener requestListener, NetworkRequestListener requestListener,
Set<VcnGatewayConnection> expectedGatewaysTornDown) { Set<VcnGatewayConnection> expectedGatewaysTornDown) {
assertFalse(mVcn.isActive()); assertFalse(mVcn.isActive());
assertTrue(mVcn.getVcnGatewayConnections().isEmpty());
for (final VcnGatewayConnection gatewayConnection : expectedGatewaysTornDown) { for (final VcnGatewayConnection gatewayConnection : expectedGatewaysTornDown) {
verify(gatewayConnection).teardownAsynchronously(); verify(gatewayConnection).teardownAsynchronously();
} }
@@ -237,6 +249,51 @@ public class VcnTest {
mGatewayStatusCallbackCaptor.capture()); mGatewayStatusCallbackCaptor.capture());
} }
@Test
public void testGatewayQuitWhileInactive() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
final Set<VcnGatewayConnection> gatewayConnections =
new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
mVcn.teardownAsynchronously();
mTestLooper.dispatchAll();
final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
statusCallback.onQuit();
mTestLooper.dispatchAll();
// Verify that the VCN requests the networkRequests be resent
assertEquals(1, mVcn.getVcnGatewayConnections().size());
verify(mVcnNetworkProvider, never()).resendAllRequests(requestListener);
}
@Test
public void testUpdateConfigReevaluatesGatewayConnections() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
startGatewaysAndGetGatewayConnections(requestListener);
assertEquals(2, mVcn.getVcnGatewayConnectionConfigMap().size());
// Create VcnConfig with only one VcnGatewayConnectionConfig so a gateway connection is torn
// down
final VcnGatewayConnectionConfig activeConfig =
VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(TEST_CAPS[0]);
final VcnGatewayConnectionConfig removedConfig =
VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(TEST_CAPS[1]);
final VcnConfig updatedConfig =
new VcnConfig.Builder(mContext).addGatewayConnectionConfig(activeConfig).build();
mVcn.updateConfig(updatedConfig);
mTestLooper.dispatchAll();
final VcnGatewayConnection activeGatewayConnection =
mVcn.getVcnGatewayConnectionConfigMap().get(activeConfig);
final VcnGatewayConnection removedGatewayConnection =
mVcn.getVcnGatewayConnectionConfigMap().get(removedConfig);
verify(activeGatewayConnection, never()).teardownAsynchronously();
verify(removedGatewayConnection).teardownAsynchronously();
verify(mVcnNetworkProvider).resendAllRequests(requestListener);
}
@Test @Test
public void testUpdateConfigExitsSafeMode() { public void testUpdateConfigExitsSafeMode() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener(); final NetworkRequestListener requestListener = verifyAndGetRequestListener();
@@ -261,8 +318,8 @@ public class VcnTest {
verify(mVcnNetworkProvider, times(2)).registerListener(eq(requestListener)); verify(mVcnNetworkProvider, times(2)).registerListener(eq(requestListener));
assertTrue(mVcn.isActive()); assertTrue(mVcn.isActive());
for (final int[] caps : TEST_CAPS) { for (final int[] caps : TEST_CAPS) {
// Expect each gateway connection created on initial startup, and again with new configs // Expect each gateway connection created only on initial startup
verify(mDeps, times(2)) verify(mDeps)
.newVcnGatewayConnection( .newVcnGatewayConnection(
eq(mVcnContext), eq(mVcnContext),
eq(TEST_SUB_GROUP), eq(TEST_SUB_GROUP),
@@ -271,4 +328,14 @@ public class VcnTest {
any()); any());
} }
} }
@Test
public void testIgnoreNetworkRequestWhileInactive() {
mVcn.setIsActive(false /* isActive */);
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
triggerVcnRequestListeners(requestListener);
verify(mDeps, never()).newVcnGatewayConnection(any(), any(), any(), any(), any());
}
} }