Merge "Data stall detection using DNS event" am: 33fe80f927

am: 69b60f5a31

Change-Id: I35e7e81fa0d6db6527ea6dd606d00046df3b9818
This commit is contained in:
Chiachang Wang
2018-11-27 20:18:54 -08:00
committed by android-build-merger
6 changed files with 400 additions and 1 deletions

View File

@@ -44,6 +44,8 @@ public final class NetworkEvent implements Parcelable {
public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10;
public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11;
public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12;
@IntDef(value = {
NETWORK_CONNECTED,
NETWORK_VALIDATED,
@@ -56,6 +58,7 @@ public final class NetworkEvent implements Parcelable {
NETWORK_REVALIDATION_SUCCESS,
NETWORK_FIRST_VALIDATION_PORTAL_FOUND,
NETWORK_REVALIDATION_PORTAL_FOUND,
NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}

View File

@@ -10439,6 +10439,41 @@ public final class Settings {
*/
public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
/**
* The threshold value for the number of consecutive dns timeout events received to be a
* signal of data stall. Set the value to 0 or less than 0 to disable. Note that the value
* should be larger than 0 if the DNS data stall detection is enabled.
*
* @hide
*/
public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD =
"data_stall_consecutive_dns_timeout_threshold";
/**
* The minimal time interval in milliseconds for data stall reevaluation.
*
* @hide
*/
public static final String DATA_STALL_MIN_EVALUATE_INTERVAL =
"data_stall_min_evaluate_interval";
/**
* DNS timeouts older than this timeout (in milliseconds) are not considered for detecting
* a data stall.
*
* @hide
*/
public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD =
"data_stall_valid_dns_time_threshold";
/**
* Which data stall detection signal to use. Possible values are a union of the powers of 2
* of DATA_STALL_EVALUATION_TYPE_*.
*
* @hide
*/
public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type";
/**
* Whether network service discovery is enabled.
*

View File

@@ -185,6 +185,10 @@ public class SettingsBackupTest {
Settings.Global.DATA_ROAMING,
Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD,
Settings.Global.DATA_STALL_EVALUATION_TYPE,
Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL,
Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD,
Settings.Global.DEBUG_APP,
Settings.Global.DEBUG_VIEW_ATTRIBUTES,
Settings.Global.DEFAULT_DNS_SERVER,

View File

@@ -1667,6 +1667,24 @@ public class ConnectivityService extends IConnectivityManager.Stub
loge("Error parsing ip address in validation event");
}
}
@Override
public void onDnsEvent(int netId, int eventType, int returnCode, String hostname,
String[] ipAddresses, int ipAddressesCount, long timestamp, int uid) {
NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
// Netd event only allow registrants from system. Each NetworkMonitor thread is under
// the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd
// event callback for certain nai. e.g. cellular. Register here to pass to
// NetworkMonitor instead.
// TODO: Move the Dns Event to NetworkMonitor. Use Binder.clearCallingIdentity() in
// registerNetworkAgent to have NetworkMonitor created with system process as design
// expectation. Also, NetdEventListenerService only allow one callback from each
// caller type. Need to re-factor NetdEventListenerService to allow multiple
// NetworkMonitor registrants.
if (nai != null && nai.satisfies(mDefaultRequest)) {
nai.networkMonitor.sendMessage(NetworkMonitor.EVENT_DNS_NOTIFICATION, returnCode);
}
}
};
@VisibleForTesting

View File

@@ -72,6 +72,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Protocol;
import com.android.internal.util.RingBufferIndices;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
@@ -99,7 +100,7 @@ public class NetworkMonitor extends StateMachine {
private static final String TAG = NetworkMonitor.class.getSimpleName();
private static final boolean DBG = true;
private static final boolean VDBG = false;
private static final boolean VDBG_STALL = Log.isLoggable(TAG, Log.DEBUG);
// Default configuration values for captive portal detection probes.
// TODO: append a random length parameter to the default HTTPS url.
// TODO: randomize browser version ids in the default User-Agent String.
@@ -116,6 +117,15 @@ public class NetworkMonitor extends StateMachine {
private static final int SOCKET_TIMEOUT_MS = 10000;
private static final int PROBE_TIMEOUT_MS = 3000;
// Default configuration values for data stall detection.
private static final int DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = 5;
private static final int DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS = 60 * 1000;
private static final int DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS = 30 * 60 * 1000;
private static final int DATA_STALL_EVALUATION_TYPE_DNS = 1;
private static final int DEFAULT_DATA_STALL_EVALUATION_TYPES =
(1 << DATA_STALL_EVALUATION_TYPE_DNS);
static enum EvaluationResult {
VALIDATED(true),
CAPTIVE_PORTAL(false);
@@ -233,6 +243,12 @@ public class NetworkMonitor extends StateMachine {
*/
public static final int CMD_PROBE_COMPLETE = BASE + 16;
/**
* ConnectivityService notifies NetworkMonitor of DNS query responses event.
* arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query.
*/
public static final int EVENT_DNS_NOTIFICATION = BASE + 17;
// Start mReevaluateDelayMs at this value and double.
private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
private static final int MAX_REEVALUATE_DELAY_MS = 10*60*1000;
@@ -314,6 +330,12 @@ public class NetworkMonitor extends StateMachine {
private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
private int mEvaluateAttempts = 0;
private volatile int mProbeToken = 0;
private final int mConsecutiveDnsTimeoutThreshold;
private final int mDataStallMinEvaluateTime;
private final int mDataStallValidDnsTimeThreshold;
private final int mDataStallEvaluationType;
private final DnsStallDetector mDnsStallDetector;
private long mLastProbeTime;
public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
NetworkRequest defaultRequest) {
@@ -359,6 +381,12 @@ public class NetworkMonitor extends StateMachine {
mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
mRandom = deps.getRandom();
// TODO: Evaluate to move data stall configuration to a specific class.
mConsecutiveDnsTimeoutThreshold = getConsecutiveDnsTimeoutThreshold();
mDnsStallDetector = new DnsStallDetector(mConsecutiveDnsTimeoutThreshold);
mDataStallMinEvaluateTime = getDataStallMinEvaluateTime();
mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
mDataStallEvaluationType = getDataStallEvalutionType();
start();
}
@@ -507,6 +535,9 @@ public class NetworkMonitor extends StateMachine {
sendMessage(CMD_EVALUATE_PRIVATE_DNS);
break;
}
case EVENT_DNS_NOTIFICATION:
mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
break;
default:
break;
}
@@ -537,6 +568,13 @@ public class NetworkMonitor extends StateMachine {
case CMD_EVALUATE_PRIVATE_DNS:
transitionTo(mEvaluatingPrivateDnsState);
break;
case EVENT_DNS_NOTIFICATION:
mDnsStallDetector.accumulateConsecutiveDnsTimeoutCount(message.arg1);
if (isDataStall()) {
validationLog("Suspecting data stall, reevaluate");
transitionTo(mEvaluatingState);
}
break;
default:
return NOT_HANDLED;
}
@@ -856,6 +894,7 @@ public class NetworkMonitor extends StateMachine {
final CaptivePortalProbeResult probeResult =
(CaptivePortalProbeResult) message.obj;
mLastProbeTime = SystemClock.elapsedRealtime();
if (probeResult.isSuccessful()) {
// Transit EvaluatingPrivateDnsState to get to Validated
// state (even if no Private DNS validation required).
@@ -883,6 +922,7 @@ public class NetworkMonitor extends StateMachine {
// Leave the event to EvaluatingState. Defer this message will result in reset
// of mReevaluateDelayMs and mEvaluateAttempts.
case CMD_NETWORK_DISCONNECTED:
case EVENT_DNS_NOTIFICATION:
return NOT_HANDLED;
default:
// TODO: Some events may able to handle in this state, instead of deferring to
@@ -947,6 +987,29 @@ public class NetworkMonitor extends StateMachine {
Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
}
private int getConsecutiveDnsTimeoutThreshold() {
return mDependencies.getSetting(mContext,
Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD,
DEFAULT_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD);
}
private int getDataStallMinEvaluateTime() {
return mDependencies.getSetting(mContext,
Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL,
DEFAULT_DATA_STALL_MIN_EVALUATE_TIME_MS);
}
private int getDataStallValidDnsTimeThreshold() {
return mDependencies.getSetting(mContext,
Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD,
DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS);
}
private int getDataStallEvalutionType() {
return mDependencies.getSetting(mContext, Settings.Global.DATA_STALL_EVALUATION_TYPE,
DEFAULT_DATA_STALL_EVALUATION_TYPES);
}
// Static for direct access by ConnectivityService
public static String getCaptivePortalServerHttpUrl(Context context) {
return getCaptivePortalServerHttpUrl(Dependencies.DEFAULT, context);
@@ -1462,4 +1525,127 @@ public class NetworkMonitor extends StateMachine {
public static final Dependencies DEFAULT = new Dependencies();
}
/**
* Methods in this class perform no locking because all accesses are performed on the state
* machine's thread. Need to consider the thread safety if it ever could be accessed outside the
* state machine.
*/
@VisibleForTesting
protected class DnsStallDetector {
private static final int DEFAULT_DNS_LOG_SIZE = 50;
private int mConsecutiveTimeoutCount = 0;
private int mSize;
final DnsResult[] mDnsEvents;
final RingBufferIndices mResultIndices;
DnsStallDetector(int size) {
mSize = Math.max(DEFAULT_DNS_LOG_SIZE, size);
mDnsEvents = new DnsResult[mSize];
mResultIndices = new RingBufferIndices(mSize);
}
@VisibleForTesting
protected void accumulateConsecutiveDnsTimeoutCount(int code) {
final DnsResult result = new DnsResult(code);
mDnsEvents[mResultIndices.add()] = result;
if (result.isTimeout()) {
mConsecutiveTimeoutCount++;
} else {
// Keep the event in mDnsEvents without clearing it so that there are logs to do the
// simulation and analysis.
mConsecutiveTimeoutCount = 0;
}
}
private boolean isDataStallSuspected(int timeoutCountThreshold, int validTime) {
if (timeoutCountThreshold <= 0) {
Log.wtf(TAG, "Timeout count threshold should be larger than 0.");
return false;
}
// Check if the consecutive timeout count reach the threshold or not.
if (mConsecutiveTimeoutCount < timeoutCountThreshold) {
return false;
}
// Check if the target dns event index is valid or not.
final int firstConsecutiveTimeoutIndex =
mResultIndices.indexOf(mResultIndices.size() - timeoutCountThreshold);
// If the dns timeout events happened long time ago, the events are meaningless for
// data stall evaluation. Thus, check if the first consecutive timeout dns event
// considered in the evaluation happened in defined threshold time.
final long now = SystemClock.elapsedRealtime();
final long firstTimeoutTime = now - mDnsEvents[firstConsecutiveTimeoutIndex].mTimeStamp;
return (firstTimeoutTime < validTime);
}
int getConsecutiveTimeoutCount() {
return mConsecutiveTimeoutCount;
}
}
private static class DnsResult {
// TODO: Need to move the DNS return code definition to a specific class once unify DNS
// response code is done.
private static final int RETURN_CODE_DNS_TIMEOUT = 255;
private final long mTimeStamp;
private final int mReturnCode;
DnsResult(int code) {
mTimeStamp = SystemClock.elapsedRealtime();
mReturnCode = code;
}
private boolean isTimeout() {
return mReturnCode == RETURN_CODE_DNS_TIMEOUT;
}
}
@VisibleForTesting
protected DnsStallDetector getDnsStallDetector() {
return mDnsStallDetector;
}
private boolean dataStallEvaluateTypeEnabled(int type) {
return (mDataStallEvaluationType & (1 << type)) != 0;
}
@VisibleForTesting
protected long getLastProbeTime() {
return mLastProbeTime;
}
@VisibleForTesting
protected boolean isDataStall() {
boolean result = false;
// Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
// possible traffic cost in metered network.
if (mNetworkAgentInfo.networkCapabilities.isMetered()
&& (SystemClock.elapsedRealtime() - getLastProbeTime()
< mDataStallMinEvaluateTime)) {
return false;
}
// Check dns signal. Suspect it may be a data stall if both :
// 1. The number of consecutive DNS query timeouts > mConsecutiveDnsTimeoutThreshold.
// 2. Those consecutive DNS queries happened in the last mValidDataStallDnsTimeThreshold ms.
if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_DNS)) {
if (mDnsStallDetector.isDataStallSuspected(mConsecutiveDnsTimeoutThreshold,
mDataStallValidDnsTimeThreshold)) {
result = true;
logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
}
}
if (VDBG_STALL) {
log("isDataStall: result=" + result + ", consecutive dns timeout count="
+ mDnsStallDetector.getConsecutiveTimeoutCount());
}
return result;
}
}

View File

@@ -40,6 +40,7 @@ import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.Settings;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -70,6 +71,7 @@ public class NetworkMonitorTest {
private @Mock Handler mHandler;
private @Mock IpConnectivityLog mLogger;
private @Mock NetworkAgentInfo mAgent;
private @Mock NetworkAgentInfo mNotMeteredAgent;
private @Mock NetworkInfo mNetworkInfo;
private @Mock NetworkRequest mRequest;
private @Mock TelephonyManager mTelephony;
@@ -87,6 +89,10 @@ public class NetworkMonitorTest {
private static final String TEST_FALLBACK_URL = "http://fallback.google.com/gen_204";
private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
private static final int DATA_STALL_EVALUATION_TYPE_DNS = 1;
private static final int RETURN_CODE_DNS_SUCCESS = 0;
private static final int RETURN_CODE_DNS_TIMEOUT = 255;
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
@@ -95,6 +101,12 @@ public class NetworkMonitorTest {
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
mAgent.networkInfo = mNetworkInfo;
mNotMeteredAgent.linkProperties = new LinkProperties();
mNotMeteredAgent.networkCapabilities = new NetworkCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
mNotMeteredAgent.networkInfo = mNetworkInfo;
when(mAgent.network()).thenReturn(mNetwork);
when(mDependencies.getNetwork(any())).thenReturn(mNetwork);
when(mDependencies.getRandom()).thenReturn(mRandom);
@@ -138,6 +150,40 @@ public class NetworkMonitorTest {
when(mNetwork.getAllByName(any())).thenReturn(new InetAddress[] {
InetAddress.parseNumericAddress("192.168.0.0")
});
setMinDataStallEvaluateInterval(500);
setDataStallEvaluationType(1 << DATA_STALL_EVALUATION_TYPE_DNS);
setValidDataStallDnsTimeThreshold(500);
setConsecutiveDnsTimeoutThreshold(5);
}
private class WrappedNetworkMonitor extends NetworkMonitor {
private long mProbeTime = 0;
WrappedNetworkMonitor(Context context, Handler handler,
NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
IpConnectivityLog logger, Dependencies deps) {
super(context, handler, networkAgentInfo, defaultRequest, logger, deps);
}
@Override
protected long getLastProbeTime() {
return mProbeTime;
}
protected void setLastProbeTime(long time) {
mProbeTime = time;
}
}
WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
return new WrappedNetworkMonitor(
mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
}
WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
return new WrappedNetworkMonitor(
mContext, mHandler, mNotMeteredAgent, mRequest, mLogger, mDependencies);
}
NetworkMonitor makeMonitor() {
@@ -272,6 +318,113 @@ public class NetworkMonitorTest {
assertPortal(makeMonitor().isCaptivePortal());
}
@Test
public void testIsDataStall_EvaluationDisabled() {
setDataStallEvaluationType(0);
WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
assertFalse(wrappedMonitor.isDataStall());
}
@Test
public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() {
WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor();
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
makeDnsTimeoutEvent(wrappedMonitor, 5);
assertTrue(wrappedMonitor.isDataStall());
}
@Test
public void testIsDataStall_EvaluationDnsOnMeteredNetwork() {
WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
assertFalse(wrappedMonitor.isDataStall());
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
makeDnsTimeoutEvent(wrappedMonitor, 5);
assertTrue(wrappedMonitor.isDataStall());
}
@Test
public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() {
WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
makeDnsTimeoutEvent(wrappedMonitor, 3);
assertFalse(wrappedMonitor.isDataStall());
// Reset consecutive timeout counts.
makeDnsSuccessEvent(wrappedMonitor, 1);
makeDnsTimeoutEvent(wrappedMonitor, 2);
assertFalse(wrappedMonitor.isDataStall());
makeDnsTimeoutEvent(wrappedMonitor, 3);
assertTrue(wrappedMonitor.isDataStall());
// Set the value to larger than the default dns log size.
setConsecutiveDnsTimeoutThreshold(51);
wrappedMonitor = makeMeteredWrappedNetworkMonitor();
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
makeDnsTimeoutEvent(wrappedMonitor, 50);
assertFalse(wrappedMonitor.isDataStall());
makeDnsTimeoutEvent(wrappedMonitor, 1);
assertTrue(wrappedMonitor.isDataStall());
}
@Test
public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() {
// Test dns events happened in valid dns time threshold.
WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
makeDnsTimeoutEvent(wrappedMonitor, 5);
assertFalse(wrappedMonitor.isDataStall());
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
assertTrue(wrappedMonitor.isDataStall());
// Test dns events happened before valid dns time threshold.
setValidDataStallDnsTimeThreshold(0);
wrappedMonitor = makeMeteredWrappedNetworkMonitor();
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100);
makeDnsTimeoutEvent(wrappedMonitor, 5);
assertFalse(wrappedMonitor.isDataStall());
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
assertFalse(wrappedMonitor.isDataStall());
}
private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
for (int i = 0; i < count; i++) {
wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
RETURN_CODE_DNS_TIMEOUT);
}
}
private void makeDnsSuccessEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
for (int i = 0; i < count; i++) {
wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
RETURN_CODE_DNS_SUCCESS);
}
}
private void setDataStallEvaluationType(int type) {
when(mDependencies.getSetting(any(),
eq(Settings.Global.DATA_STALL_EVALUATION_TYPE), anyInt())).thenReturn(type);
}
private void setMinDataStallEvaluateInterval(int time) {
when(mDependencies.getSetting(any(),
eq(Settings.Global.DATA_STALL_MIN_EVALUATE_INTERVAL), anyInt())).thenReturn(time);
}
private void setValidDataStallDnsTimeThreshold(int time) {
when(mDependencies.getSetting(any(),
eq(Settings.Global.DATA_STALL_VALID_DNS_TIME_THRESHOLD), anyInt())).thenReturn(time);
}
private void setConsecutiveDnsTimeoutThreshold(int num) {
when(mDependencies.getSetting(any(),
eq(Settings.Global.DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD), anyInt()))
.thenReturn(num);
}
private void setFallbackUrl(String url) {
when(mDependencies.getSetting(any(),
eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL), any())).thenReturn(url);