Skip to content

Commit 84b74b3

Browse files
authored
HDFS-15447 RBF: Add top real owners metrics for delegation tokens (#2110)
1 parent 3e70006 commit 84b74b3

File tree

8 files changed

+209
-5
lines changed

8 files changed

+209
-5
lines changed

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenSecretManager.java

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
import java.io.DataInputStream;
2323
import java.io.IOException;
2424
import java.security.MessageDigest;
25+
import java.util.ArrayList;
2526
import java.util.Collection;
27+
import java.util.Collections;
2628
import java.util.HashSet;
2729
import java.util.Iterator;
30+
import java.util.List;
2831
import java.util.Map;
2932
import java.util.Set;
3033
import java.util.concurrent.ConcurrentHashMap;
@@ -34,6 +37,8 @@
3437
import org.apache.hadoop.classification.InterfaceAudience;
3538
import org.apache.hadoop.classification.InterfaceStability;
3639
import org.apache.hadoop.io.Text;
40+
import org.apache.hadoop.metrics2.util.Metrics2Util.NameValuePair;
41+
import org.apache.hadoop.metrics2.util.Metrics2Util.TopN;
3742
import org.apache.hadoop.security.AccessControlException;
3843
import org.apache.hadoop.security.HadoopKerberosName;
3944
import org.apache.hadoop.security.token.SecretManager;
@@ -64,7 +69,13 @@ private String formatTokenId(TokenIdent id) {
6469
*/
6570
protected final Map<TokenIdent, DelegationTokenInformation> currentTokens
6671
= new ConcurrentHashMap<>();
67-
72+
73+
/**
74+
* Map of token real owners to its token count. This is used to generate
75+
* metrics of top users by owned tokens.
76+
*/
77+
protected final Map<String, Long> tokenOwnerStats = new ConcurrentHashMap<>();
78+
6879
/**
6980
* Sequence number to create DelegationTokenIdentifier.
7081
* Protected by this object lock.
@@ -292,6 +303,7 @@ protected DelegationTokenInformation getTokenInfo(TokenIdent ident) {
292303
protected void storeToken(TokenIdent ident,
293304
DelegationTokenInformation tokenInfo) throws IOException {
294305
currentTokens.put(ident, tokenInfo);
306+
addTokenForOwnerStats(ident);
295307
storeNewToken(ident, tokenInfo.getRenewDate());
296308
}
297309

@@ -339,6 +351,7 @@ public synchronized void addPersistedDelegationToken(
339351
if (getTokenInfo(identifier) == null) {
340352
currentTokens.put(identifier, new DelegationTokenInformation(renewDate,
341353
password, getTrackingIdIfEnabled(identifier)));
354+
addTokenForOwnerStats(identifier);
342355
} else {
343356
throw new IOException("Same delegation token being added twice: "
344357
+ formatTokenId(identifier));
@@ -578,6 +591,7 @@ public synchronized TokenIdent cancelToken(Token<TokenIdent> token,
578591
if (info == null) {
579592
throw new InvalidToken("Token not found " + formatTokenId(id));
580593
}
594+
removeTokenForOwnerStats(id);
581595
removeStoredToken(id);
582596
return id;
583597
}
@@ -634,6 +648,7 @@ private void removeExpiredToken() throws IOException {
634648
long renewDate = entry.getValue().getRenewDate();
635649
if (renewDate < now) {
636650
expiredTokens.add(entry.getKey());
651+
removeTokenForOwnerStats(entry.getKey());
637652
i.remove();
638653
}
639654
}
@@ -726,4 +741,88 @@ public TokenIdent decodeTokenIdentifier(Token<TokenIdent> token) throws IOExcept
726741
return token.decodeIdentifier();
727742
}
728743

744+
/**
745+
* Return top token real owners list as well as the tokens count.
746+
*
747+
* @param n top number of users
748+
* @return map of owners to counts
749+
*/
750+
public List<NameValuePair> getTopTokenRealOwners(int n) {
751+
n = Math.min(n, tokenOwnerStats.size());
752+
if (n == 0) {
753+
return new ArrayList<>();
754+
}
755+
756+
TopN topN = new TopN(n);
757+
for (Map.Entry<String, Long> entry : tokenOwnerStats.entrySet()) {
758+
topN.offer(new NameValuePair(
759+
entry.getKey(), entry.getValue()));
760+
}
761+
762+
List<NameValuePair> list = new ArrayList<>();
763+
while (!topN.isEmpty()) {
764+
list.add(topN.poll());
765+
}
766+
Collections.reverse(list);
767+
return list;
768+
}
769+
770+
/**
771+
* Return the real owner for a token. If this is a token from a proxy user,
772+
* the real/effective user will be returned.
773+
*
774+
* @param id
775+
* @return real owner
776+
*/
777+
private String getTokenRealOwner(TokenIdent id) {
778+
String realUser;
779+
if (id.getRealUser() != null && !id.getRealUser().toString().isEmpty()) {
780+
realUser = id.getRealUser().toString();
781+
} else {
782+
// if there is no real user -> this is a non proxy user
783+
// the user itself is the real owner
784+
realUser = id.getUser().getUserName();
785+
}
786+
return realUser;
787+
}
788+
789+
/**
790+
* Add token stats to the owner to token count mapping.
791+
*
792+
* @param id
793+
*/
794+
private void addTokenForOwnerStats(TokenIdent id) {
795+
String realOwner = getTokenRealOwner(id);
796+
tokenOwnerStats.put(realOwner,
797+
tokenOwnerStats.getOrDefault(realOwner, 0L)+1);
798+
}
799+
800+
/**
801+
* Remove token stats to the owner to token count mapping.
802+
*
803+
* @param id
804+
*/
805+
private void removeTokenForOwnerStats(TokenIdent id) {
806+
String realOwner = getTokenRealOwner(id);
807+
if (tokenOwnerStats.containsKey(realOwner)) {
808+
// unlikely to be less than 1 but in case
809+
if (tokenOwnerStats.get(realOwner) <= 1) {
810+
tokenOwnerStats.remove(realOwner);
811+
} else {
812+
tokenOwnerStats.put(realOwner, tokenOwnerStats.get(realOwner)-1);
813+
}
814+
}
815+
}
816+
817+
/**
818+
* This method syncs token information from currentTokens to tokenOwnerStats.
819+
* It is used when the currentTokens is initialized or refreshed. This is
820+
* called from a single thread thus no synchronization is needed.
821+
*/
822+
protected void syncTokenOwnerStats() {
823+
tokenOwnerStats.clear();
824+
for (TokenIdent id : currentTokens.keySet()) {
825+
addTokenForOwnerStats(id);
826+
}
827+
}
729828
}

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/ZKDelegationTokenSecretManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ private void loadFromZKCache(final boolean isTokenCache) {
457457
++count;
458458
}
459459
}
460+
if (isTokenCache) {
461+
syncTokenOwnerStats();
462+
}
460463
if (count > 0) {
461464
LOG.warn("Ignored {} nodes while loading {} cache.", count, cacheName);
462465
}

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/metrics/RBFMetrics.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ public class RBFMetrics implements RouterMBean, FederationMBean {
124124
private MountTableStore mountTableStore;
125125
/** Router state store. */
126126
private RouterStore routerStore;
127-
127+
/** The number of top token owners reported in metrics. */
128+
private int topTokenRealOwners;
128129

129130
public RBFMetrics(Router router) throws IOException {
130131
this.router = router;
@@ -166,7 +167,9 @@ public RBFMetrics(Router router) throws IOException {
166167
Configuration conf = router.getConfig();
167168
this.timeOut = conf.getTimeDuration(RBFConfigKeys.DN_REPORT_TIME_OUT,
168169
RBFConfigKeys.DN_REPORT_TIME_OUT_MS_DEFAULT, TimeUnit.MILLISECONDS);
169-
170+
this.topTokenRealOwners = conf.getInt(
171+
RBFConfigKeys.DFS_ROUTER_METRICS_TOP_NUM_TOKEN_OWNERS_KEY,
172+
RBFConfigKeys.DFS_ROUTER_METRICS_TOP_NUM_TOKEN_OWNERS_KEY_DEFAULT);
170173
}
171174

172175
/**
@@ -649,6 +652,17 @@ public long getCurrentTokensCount() {
649652
return -1;
650653
}
651654

655+
@Override
656+
public String getTopTokenRealOwners() {
657+
RouterSecurityManager mgr =
658+
this.router.getRpcServer().getRouterSecurityManager();
659+
if (mgr != null && mgr.getSecretManager() != null) {
660+
return JSON.toString(mgr.getSecretManager()
661+
.getTopTokenRealOwners(this.topTokenRealOwners));
662+
}
663+
return "";
664+
}
665+
652666
@Override
653667
public boolean isSecurityEnabled() {
654668
return UserGroupInformation.isSecurityEnabled();

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/metrics/RouterMBean.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,11 @@ public interface RouterMBean {
101101
* @return true, if security is enabled.
102102
*/
103103
boolean isSecurityEnabled();
104+
105+
/**
106+
* Get the top delegation token owners(realUser).
107+
*
108+
* @return Json string of owners to token counts
109+
*/
110+
String getTopTokenRealOwners();
104111
}

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/router/RBFConfigKeys.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public class RBFConfigKeys extends CommonConfigurationKeysPublic {
7979
public static final Class<? extends RouterRpcMonitor>
8080
DFS_ROUTER_METRICS_CLASS_DEFAULT =
8181
FederationRPCPerformanceMonitor.class;
82+
public static final String DFS_ROUTER_METRICS_TOP_NUM_TOKEN_OWNERS_KEY =
83+
FEDERATION_ROUTER_PREFIX + "top.num.token.realowners";
84+
public static final int
85+
DFS_ROUTER_METRICS_TOP_NUM_TOKEN_OWNERS_KEY_DEFAULT = 10;
8286

8387
// HDFS Router heartbeat
8488
public static final String DFS_ROUTER_HEARTBEAT_ENABLE =

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/router/security/token/ZKDelegationTokenSecretManagerImpl.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
import org.slf4j.LoggerFactory;
3131
import org.apache.hadoop.conf.Configuration;
3232

33-
import java.io.ByteArrayInputStream;
34-
import java.io.DataInputStream;
3533
import java.io.IOException;
3634
import java.util.HashSet;
3735
import java.util.List;
@@ -197,6 +195,7 @@ private void rebuildTokenCache(boolean initial) throws IOException {
197195
}
198196
}
199197
}
198+
syncTokenOwnerStats();
200199
}
201200

202201
@Override

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/resources/hdfs-rbf-default.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,4 +657,14 @@
657657
</description>
658658
</property>
659659

660+
<property>
661+
<name>dfs.federation.router.top.num.token.realowners</name>
662+
<value>10</value>
663+
<description>
664+
The number of top real owners by tokens count to report in the JMX metrics.
665+
Real owners are the effective users whose cretential are used to generate
666+
the tokens.
667+
</description>
668+
</property>
669+
660670
</configuration>

hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/security/TestRouterSecurityManager.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.hadoop.hdfs.server.federation.router.Router;
2929
import org.apache.hadoop.hdfs.server.federation.router.security.token.ZKDelegationTokenSecretManagerImpl;
3030
import org.apache.hadoop.io.Text;
31+
import org.apache.hadoop.metrics2.util.Metrics2Util.NameValuePair;
3132
import org.apache.hadoop.security.Credentials;
3233
import org.apache.hadoop.security.UserGroupInformation;
3334
import org.apache.hadoop.security.token.SecretManager;
@@ -50,6 +51,7 @@
5051

5152
import org.hamcrest.core.StringContains;
5253
import java.io.IOException;
54+
import java.util.List;
5355

5456
import org.slf4j.Logger;
5557
import org.slf4j.LoggerFactory;
@@ -124,6 +126,72 @@ public void testDelegationTokens() throws IOException {
124126
securityManager.renewDelegationToken(token);
125127
}
126128

129+
@Test
130+
public void testDelgationTokenTopOwners() throws Exception {
131+
UserGroupInformation.reset();
132+
List<NameValuePair> topOwners;
133+
134+
UserGroupInformation user = UserGroupInformation
135+
.createUserForTesting("abc", new String[]{"router_group"});
136+
UserGroupInformation.setLoginUser(user);
137+
Token dt = securityManager.getDelegationToken(new Text("abc"));
138+
topOwners = securityManager.getSecretManager().getTopTokenRealOwners(2);
139+
assertEquals(1, topOwners.size());
140+
assertEquals("abc", topOwners.get(0).getName());
141+
assertEquals(1, topOwners.get(0).getValue());
142+
143+
securityManager.renewDelegationToken(dt);
144+
topOwners = securityManager.getSecretManager().getTopTokenRealOwners(2);
145+
assertEquals(1, topOwners.size());
146+
assertEquals("abc", topOwners.get(0).getName());
147+
assertEquals(1, topOwners.get(0).getValue());
148+
149+
securityManager.cancelDelegationToken(dt);
150+
topOwners = securityManager.getSecretManager().getTopTokenRealOwners(2);
151+
assertEquals(0, topOwners.size());
152+
153+
154+
// Use proxy user - the code should use the proxy user as the real owner
155+
UserGroupInformation routerUser =
156+
UserGroupInformation.createRemoteUser("router");
157+
UserGroupInformation proxyUser = UserGroupInformation
158+
.createProxyUserForTesting("abc",
159+
routerUser,
160+
new String[]{"router_group"});
161+
UserGroupInformation.setLoginUser(proxyUser);
162+
163+
Token proxyDT = securityManager.getDelegationToken(new Text("router"));
164+
topOwners = securityManager.getSecretManager().getTopTokenRealOwners(2);
165+
assertEquals(1, topOwners.size());
166+
assertEquals("router", topOwners.get(0).getName());
167+
assertEquals(1, topOwners.get(0).getValue());
168+
169+
// router to renew tokens
170+
UserGroupInformation.setLoginUser(routerUser);
171+
securityManager.renewDelegationToken(proxyDT);
172+
topOwners = securityManager.getSecretManager().getTopTokenRealOwners(2);
173+
assertEquals(1, topOwners.size());
174+
assertEquals("router", topOwners.get(0).getName());
175+
assertEquals(1, topOwners.get(0).getValue());
176+
177+
securityManager.cancelDelegationToken(proxyDT);
178+
topOwners = securityManager.getSecretManager().getTopTokenRealOwners(2);
179+
assertEquals(0, topOwners.size());
180+
181+
182+
// check rank by more users
183+
securityManager.getDelegationToken(new Text("router"));
184+
securityManager.getDelegationToken(new Text("router"));
185+
UserGroupInformation.setLoginUser(user);
186+
securityManager.getDelegationToken(new Text("router"));
187+
topOwners = securityManager.getSecretManager().getTopTokenRealOwners(2);
188+
assertEquals(2, topOwners.size());
189+
assertEquals("router", topOwners.get(0).getName());
190+
assertEquals(2, topOwners.get(0).getValue());
191+
assertEquals("abc", topOwners.get(1).getName());
192+
assertEquals(1, topOwners.get(1).getValue());
193+
}
194+
127195
@Test
128196
public void testVerifyToken() throws IOException {
129197
UserGroupInformation.reset();

0 commit comments

Comments
 (0)