-
-
Notifications
You must be signed in to change notification settings - Fork 7
feat: HBase resolvable endpoints #1159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
adwk67
wants to merge
25
commits into
main
Choose a base branch
from
feat/hbase-resolvable-endpoints
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+956
−6
Open
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
0fbe36b
patches I
adwk67 b5f96d5
patches I
adwk67 303739e
wip: patch hbase to use listener endpoints
adwk67 8dc1c99
changed rendering
adwk67 bd17a9e
rework patch for UI
adwk67 4a5df3b
Merge branch 'main' into feat/hbase-resolvable-endpoints
nightkr 2a2a5ac
Consistently override the advertised ports
nightkr 9ef046c
add listener endpoint and info port to hbase-site.xml
adwk67 99fd166
patch and entrypoint refactoring
adwk67 9953042
hbase: use listener service for region mover
adwk67 9b02ec8
Merge branch 'main' into feat/hbase-resolvable-endpoints
adwk67 4b6b849
hbase: patch for 2.6.2 endpoints
adwk67 be89d58
Merge branch 'main' into feat/hbase-resolvable-endpoints
adwk67 ceba85b
removed dead variable
adwk67 f08e1c5
Update hbase/stackable/patches/2.6.1/0005-Allow-overriding-ipc-bind-p…
adwk67 d98ac32
Update hbase/stackable/patches/2.6.1/0005-Allow-overriding-ipc-bind-p…
adwk67 bd0b75e
review comments: added constants etc.
adwk67 a6a7e10
merge main and fix conflicts
adwk67 de0c6c7
set RPC_CLIENT_SPECIFY_HOST to true by default
adwk67 496444d
corrected constant descriptions
adwk67 3fa6301
updated 2.6.2 patch
adwk67 ee7ea2e
patch 2.6.1 to reverse bound/advertised properties
adwk67 7a4340e
property re-working for 2.6.2
adwk67 f3d3596
Merge branch 'main' into feat/hbase-resolvable-endpoints
adwk67 65aaae2
provide advertised port as a fallback, removed changes to test class
adwk67 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
296 changes: 296 additions & 0 deletions
296
...e/stackable/patches/2.6.1/0005-Allow-overriding-ipc-bind-port-and-use-alternative-p.patch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
From 7014c24f4441278dd366c888fc0c05ce53fe9c06 Mon Sep 17 00:00:00 2001 | ||
From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= <nat@nullable.se> | ||
Date: Fri, 30 May 2025 14:26:26 +0200 | ||
Subject: Allow overriding ipc bind port and use alternative port from listener | ||
|
||
--- | ||
.../org/apache/hadoop/hbase/HConstants.java | 21 ++++++++++ | ||
.../apache/hadoop/hbase/master/HMaster.java | 20 +++++++-- | ||
.../hbase/regionserver/HRegionServer.java | 41 +++++++++++++++---- | ||
.../hbase/regionserver/RSRpcServices.java | 8 +++- | ||
4 files changed, 76 insertions(+), 14 deletions(-) | ||
|
||
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java | ||
index 3b2a58827f..1ba1feefcb 100644 | ||
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java | ||
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java | ||
@@ -197,6 +197,12 @@ public final class HConstants { | ||
/** Parameter name for port master listens on. */ | ||
public static final String MASTER_PORT = "hbase.master.port"; | ||
|
||
+ /** Parameter name for master IPC address */ | ||
nightkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
+ public static final String MASTER_IPC_ADDRESS = "hbase.master.ipc.address"; | ||
+ | ||
+ /** Parameter name for master IPC port */ | ||
nightkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
+ public static final String MASTER_IPC_PORT = "hbase.master.ipc.port"; | ||
+ | ||
/** default port that the master listens on */ | ||
public static final int DEFAULT_MASTER_PORT = 16000; | ||
|
||
@@ -206,6 +212,9 @@ public final class HConstants { | ||
/** Configuration key for master web API port */ | ||
nightkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public static final String MASTER_INFO_PORT = "hbase.master.info.port"; | ||
|
||
+ /** Configuration key for bound master web API port */ | ||
nightkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
+ public static final String MASTER_BOUND_INFO_PORT = "hbase.master.bound.info.port"; | ||
+ | ||
/** Configuration key for the list of master host:ports **/ | ||
public static final String MASTER_ADDRS_KEY = "hbase.masters"; | ||
|
||
@@ -316,6 +325,12 @@ public final class HConstants { | ||
/** Parameter name for port region server listens on. */ | ||
public static final String REGIONSERVER_PORT = "hbase.regionserver.port"; | ||
|
||
+ /** Parameter name for master IPC address */ | ||
+ public static final String REGIONSERVER_IPC_ADDRESS = "hbase.regionserver.ipc.address"; | ||
+ | ||
+ /** Parameter name for master IPC port */ | ||
+ public static final String REGIONSERVER_IPC_PORT = "hbase.regionserver.ipc.port"; | ||
+ | ||
/** Default port region server listens on. */ | ||
public static final int DEFAULT_REGIONSERVER_PORT = 16020; | ||
|
||
@@ -325,6 +340,9 @@ public final class HConstants { | ||
/** A configuration key for regionserver info port */ | ||
public static final String REGIONSERVER_INFO_PORT = "hbase.regionserver.info.port"; | ||
|
||
+ /** A configuration key for bound regionserver hbase info port */ | ||
+ public static final String REGIONSERVER_BOUND_INFO_PORT = "hbase.regionserver.bound.info.port"; | ||
nightkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
+ | ||
/** A flag that enables automatic selection of regionserver info port */ | ||
public static final String REGIONSERVER_INFO_PORT_AUTO = REGIONSERVER_INFO_PORT + ".auto"; | ||
|
||
@@ -1392,6 +1410,9 @@ public final class HConstants { | ||
/** Configuration key for setting RPC codec class name */ | ||
public static final String RPC_CODEC_CONF_KEY = "hbase.client.rpc.codec"; | ||
|
||
+ /** Configuration key for setting that the RPC client should specify the host */ | ||
+ public static final String RPC_CLIENT_SPECIFY_HOST = "hbase.rpc.client.specify.host"; | ||
nightkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
+ | ||
/** Configuration key for setting replication codec class name */ | ||
public static final String REPLICATION_CODEC_CONF_KEY = "hbase.replication.rpc.codec"; | ||
|
||
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java | ||
index 3fe5abac27..2f323518da 100644 | ||
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java | ||
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java | ||
@@ -20,6 +20,8 @@ package org.apache.hadoop.hbase.master; | ||
import static org.apache.hadoop.hbase.HConstants.DEFAULT_HBASE_SPLIT_COORDINATED_BY_ZK; | ||
import static org.apache.hadoop.hbase.HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS; | ||
import static org.apache.hadoop.hbase.HConstants.HBASE_SPLIT_WAL_COORDINATED_BY_ZK; | ||
+import static org.apache.hadoop.hbase.HConstants.MASTER_BOUND_INFO_PORT; | ||
+import static org.apache.hadoop.hbase.HConstants.MASTER_PORT; | ||
import static org.apache.hadoop.hbase.master.cleaner.HFileCleaner.CUSTOM_POOL_SIZE; | ||
import static org.apache.hadoop.hbase.util.DNS.MASTER_HOSTNAME_KEY; | ||
|
||
@@ -559,6 +561,18 @@ public class HMaster extends HRegionServer implements MasterServices { | ||
return conf.get(MASTER_HOSTNAME_KEY); | ||
} | ||
|
||
+ @Override | ||
+ protected int getUseThisPortInstead(Configuration conf) { | ||
+ int port = conf.getInt(MASTER_PORT, 0); | ||
+ return port != 0 ? port : this.rpcServices.getSocketAddress().getPort(); | ||
+ } | ||
+ | ||
+ @Override | ||
+ protected int getUseThisInfoPortInstead(Configuration conf) { | ||
+ int port = conf.getInt(MASTER_BOUND_INFO_PORT, 0); | ||
+ return port != 0 ? port : this.infoServer != null ? this.infoServer.getPort() : -1; | ||
+ } | ||
+ | ||
private void registerConfigurationObservers() { | ||
configurationManager.registerObserver(this.rpcServices); | ||
configurationManager.registerObserver(this); | ||
@@ -586,8 +600,8 @@ public class HMaster extends HRegionServer implements MasterServices { | ||
registerConfigurationObservers(); | ||
Threads.setDaemonThreadRunning(new Thread(() -> TraceUtil.trace(() -> { | ||
try { | ||
- int infoPort = putUpJettyServer(); | ||
adwk67 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- startActiveMasterManager(infoPort); | ||
+ putUpJettyServer(); | ||
+ startActiveMasterManager(useThisInfoPortInstead); | ||
} catch (Throwable t) { | ||
// Make sure we log the exception. | ||
String error = "Failed to become Active Master"; | ||
@@ -2991,7 +3005,7 @@ public class HMaster extends HRegionServer implements MasterServices { | ||
} | ||
case MASTER_INFO_PORT: { | ||
if (infoServer != null) { | ||
- builder.setMasterInfoPort(infoServer.getPort()); | ||
+ builder.setMasterInfoPort(useThisInfoPortInstead); | ||
} | ||
break; | ||
} | ||
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java | ||
index 27bcef2f06..ff8b3b05f6 100644 | ||
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java | ||
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java | ||
@@ -24,6 +24,9 @@ import static org.apache.hadoop.hbase.HConstants.DEFAULT_HBASE_SPLIT_WAL_MAX_SPL | ||
import static org.apache.hadoop.hbase.HConstants.DEFAULT_SLOW_LOG_SYS_TABLE_CHORE_DURATION; | ||
import static org.apache.hadoop.hbase.HConstants.HBASE_SPLIT_WAL_COORDINATED_BY_ZK; | ||
import static org.apache.hadoop.hbase.HConstants.HBASE_SPLIT_WAL_MAX_SPLITTER; | ||
+import static org.apache.hadoop.hbase.HConstants.REGIONSERVER_BOUND_INFO_PORT; | ||
+import static org.apache.hadoop.hbase.HConstants.REGIONSERVER_PORT; | ||
+import static org.apache.hadoop.hbase.HConstants.RPC_CLIENT_SPECIFY_HOST; | ||
import static org.apache.hadoop.hbase.master.waleventtracker.WALEventTrackerTableCreator.WAL_EVENT_TRACKER_ENABLED_DEFAULT; | ||
import static org.apache.hadoop.hbase.master.waleventtracker.WALEventTrackerTableCreator.WAL_EVENT_TRACKER_ENABLED_KEY; | ||
import static org.apache.hadoop.hbase.namequeues.NamedQueueServiceChore.NAMED_QUEUE_CHORE_DURATION_DEFAULT; | ||
@@ -505,6 +508,10 @@ public class HRegionServer extends Thread | ||
*/ | ||
protected String useThisHostnameInstead; | ||
|
||
+ protected int useThisPortInstead; | ||
+ | ||
+ protected int useThisInfoPortInstead; | ||
+ | ||
/** | ||
* @deprecated since 2.4.0 and will be removed in 4.0.0. Use | ||
* {@link HRegionServer#UNSAFE_RS_HOSTNAME_DISABLE_MASTER_REVERSEDNS_KEY} instead. | ||
@@ -669,6 +676,8 @@ public class HRegionServer extends Thread | ||
this.namedQueueRecorder = NamedQueueRecorder.getInstance(this.conf); | ||
rpcServices = createRpcServices(); | ||
useThisHostnameInstead = getUseThisHostnameInstead(conf); | ||
+ useThisPortInstead = getUseThisPortInstead(conf); | ||
+ useThisInfoPortInstead = getUseThisInfoPortInstead(conf); //conf.getInt("hbase.info.port" , this.infoServer != null ? this.infoServer.getPort() : -1); | ||
nightkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// if use-ip is enabled, we will use ip to expose Master/RS service for client, | ||
// see HBASE-27304 for details. | ||
@@ -678,7 +687,7 @@ public class HRegionServer extends Thread | ||
useIp ? rpcServices.isa.getAddress().getHostAddress() : rpcServices.isa.getHostName(); | ||
String hostName = | ||
StringUtils.isBlank(useThisHostnameInstead) ? isaHostName : useThisHostnameInstead; | ||
- serverName = ServerName.valueOf(hostName, this.rpcServices.isa.getPort(), this.startcode); | ||
+ serverName = ServerName.valueOf(hostName, useThisPortInstead, this.startcode); | ||
|
||
rpcControllerFactory = RpcControllerFactory.instantiate(this.conf); | ||
rpcRetryingCallerFactory = RpcRetryingCallerFactory.instantiate(this.conf, | ||
@@ -715,7 +724,7 @@ public class HRegionServer extends Thread | ||
|
||
// Some unit tests don't need a cluster, so no zookeeper at all | ||
// Open connection to zookeeper and set primary watcher | ||
- zooKeeper = new ZKWatcher(conf, getProcessName() + ":" + rpcServices.isa.getPort(), this, | ||
+ zooKeeper = new ZKWatcher(conf, getProcessName() + ":" + useThisPortInstead, this, | ||
canCreateBaseZNode()); | ||
// If no master in cluster, skip trying to track one or look for a cluster status. | ||
if (!this.masterless) { | ||
@@ -776,6 +785,16 @@ public class HRegionServer extends Thread | ||
} | ||
} | ||
|
||
+ protected int getUseThisPortInstead(Configuration conf) { | ||
+ int port = conf.getInt(REGIONSERVER_PORT, 0); | ||
+ return port != 0 ? port : this.rpcServices.isa.getPort(); | ||
+ } | ||
+ | ||
+ protected int getUseThisInfoPortInstead(Configuration conf) { | ||
+ int port = conf.getInt(REGIONSERVER_BOUND_INFO_PORT, 0); | ||
+ return port != 0 ? port : this.infoServer != null ? this.infoServer.getPort() : -1; | ||
+ } | ||
+ | ||
private void setupSignalHandlers() { | ||
if (!SystemUtils.IS_OS_WINDOWS) { | ||
HBasePlatformDependent.handle("HUP", (number, name) -> { | ||
@@ -957,8 +976,7 @@ public class HRegionServer extends Thread | ||
bootstrapNodeManager = new BootstrapNodeManager(clusterConnection, masterAddressTracker); | ||
} | ||
// Setup RPC client for master communication | ||
- this.rpcClient = RpcClientFactory.createClient(conf, clusterId, | ||
- new InetSocketAddress(this.rpcServices.isa.getAddress(), 0), | ||
+ this.rpcClient = RpcClientFactory.createClient(conf, clusterId, getInetSocketAddress(this.conf), | ||
clusterConnection.getConnectionMetrics(), Collections.emptyMap()); | ||
span.setStatus(StatusCode.OK); | ||
} catch (Throwable t) { | ||
@@ -972,6 +990,11 @@ public class HRegionServer extends Thread | ||
} | ||
} | ||
|
||
+ private InetSocketAddress getInetSocketAddress(Configuration conf) { | ||
+ return conf.getBoolean(RPC_CLIENT_SPECIFY_HOST, false) ? | ||
+ new InetSocketAddress(this.rpcServices.isa.getAddress(), 0) : new InetSocketAddress(0); | ||
+ } | ||
+ | ||
/** | ||
* Bring up connection to zk ensemble and then wait until a master for this cluster and then after | ||
* that, wait until cluster 'up' flag has been set. This is the order in which master does things. | ||
@@ -1533,6 +1556,7 @@ public class HRegionServer extends Thread | ||
} else { | ||
serverLoad.setInfoServerPort(-1); | ||
} | ||
+ serverLoad.setInfoServerPort(useThisInfoPortInstead); | ||
MetricsUserAggregateSource userSource = | ||
metricsRegionServer.getMetricsUserAggregate().getSource(); | ||
if (userSource != null) { | ||
@@ -1688,7 +1712,7 @@ public class HRegionServer extends Thread | ||
if (key.equals(HConstants.KEY_FOR_HOSTNAME_SEEN_BY_MASTER)) { | ||
String hostnameFromMasterPOV = e.getValue(); | ||
this.serverName = ServerName.valueOf(hostnameFromMasterPOV, | ||
- rpcServices.getSocketAddress().getPort(), this.startcode); | ||
+ useThisPortInstead, this.startcode); | ||
String expectedHostName = rpcServices.getSocketAddress().getHostName(); | ||
// if Master use-ip is enabled, RegionServer use-ip will be enabled by default even if it | ||
// is set to disable. so we will use the ip of the RegionServer to compare with the | ||
@@ -1814,7 +1838,7 @@ public class HRegionServer extends Thread | ||
|
||
private void createMyEphemeralNode() throws KeeperException { | ||
RegionServerInfo.Builder rsInfo = RegionServerInfo.newBuilder(); | ||
- rsInfo.setInfoPort(infoServer != null ? infoServer.getPort() : -1); | ||
+ rsInfo.setInfoPort(infoServer != null ? useThisInfoPortInstead : -1); | ||
rsInfo.setVersionInfo(ProtobufUtil.getVersionInfo()); | ||
byte[] data = ProtobufUtil.prependPBMagic(rsInfo.build().toByteArray()); | ||
ZKUtil.createEphemeralNodeAndWatch(this.zooKeeper, getMyEphemeralNodePath(), data); | ||
@@ -2479,7 +2503,7 @@ public class HRegionServer extends Thread | ||
LOG.info("Retry starting http info server with port: " + port); | ||
} | ||
} | ||
- port = this.infoServer.getPort(); | ||
+ port = useThisInfoPortInstead; | ||
conf.setInt(HConstants.REGIONSERVER_INFO_PORT, port); | ||
int masterInfoPort = | ||
conf.getInt(HConstants.MASTER_INFO_PORT, HConstants.DEFAULT_MASTER_INFOPORT); | ||
@@ -3073,12 +3097,11 @@ public class HRegionServer extends Thread | ||
LOG.info("reportForDuty to master=" + masterServerName + " with isa=" + rpcServices.isa | ||
+ ", startcode=" + this.startcode); | ||
long now = EnvironmentEdgeManager.currentTime(); | ||
- int port = rpcServices.isa.getPort(); | ||
RegionServerStartupRequest.Builder request = RegionServerStartupRequest.newBuilder(); | ||
if (!StringUtils.isBlank(useThisHostnameInstead)) { | ||
request.setUseThisHostnameInstead(useThisHostnameInstead); | ||
} | ||
- request.setPort(port); | ||
+ request.setPort(useThisPortInstead); | ||
request.setServerStartCode(this.startcode); | ||
request.setServerCurrentTime(now); | ||
result = rss.regionServerStartup(null, request.build()); | ||
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java | ||
index b77fcf338a..a86cd273ff 100644 | ||
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java | ||
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java | ||
@@ -280,6 +280,10 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.BulkLoadDescr | ||
import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.CompactionDescriptor; | ||
import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.FlushDescriptor; | ||
import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.RegionEventDescriptor; | ||
+import static org.apache.hadoop.hbase.HConstants.MASTER_IPC_ADDRESS; | ||
+import static org.apache.hadoop.hbase.HConstants.MASTER_IPC_PORT; | ||
+import static org.apache.hadoop.hbase.HConstants.REGIONSERVER_IPC_ADDRESS; | ||
+import static org.apache.hadoop.hbase.HConstants.REGIONSERVER_IPC_PORT; | ||
|
||
/** | ||
* Implements the regionserver RPC services. | ||
@@ -1270,14 +1274,14 @@ public class RSRpcServices implements HBaseRPCErrorHandler, AdminService.Blockin | ||
int port = conf.getInt(HConstants.MASTER_PORT, HConstants.DEFAULT_MASTER_PORT); | ||
// Creation of a HSA will force a resolve. | ||
initialIsa = new InetSocketAddress(hostname, port); | ||
- bindAddress = new InetSocketAddress(conf.get("hbase.master.ipc.address", hostname), port); | ||
+ bindAddress = new InetSocketAddress(conf.get(MASTER_IPC_ADDRESS, hostname), conf.getInt(MASTER_IPC_PORT, port)); | ||
} else { | ||
String hostname = DNS.getHostname(conf, DNS.ServerType.REGIONSERVER); | ||
int port = conf.getInt(HConstants.REGIONSERVER_PORT, HConstants.DEFAULT_REGIONSERVER_PORT); | ||
// Creation of a HSA will force a resolve. | ||
initialIsa = new InetSocketAddress(hostname, port); | ||
bindAddress = | ||
- new InetSocketAddress(conf.get("hbase.regionserver.ipc.address", hostname), port); | ||
+ new InetSocketAddress(conf.get(REGIONSERVER_IPC_ADDRESS, hostname), conf.getInt(REGIONSERVER_IPC_PORT, port)); | ||
} | ||
if (initialIsa.getAddress() == null) { | ||
throw new IllegalArgumentException("Failed resolve of " + initialIsa); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.