Skip to content

Commit c4fb404

Browse files
committed
YARN-10415. Create a group matcher which checks ALL groups of the user. Contributed by Gergely Pollak.
1 parent ac7d462 commit c4fb404

File tree

8 files changed

+220
-24
lines changed

8 files changed

+220
-24
lines changed

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/CSMappingPlacementRule.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -156,23 +156,24 @@ public boolean initialize(ResourceScheduler scheduler) throws IOException {
156156
return mappingRules.size() > 0;
157157
}
158158

159-
private String getPrimaryGroup(String user) throws IOException {
160-
return groups.getGroupsSet(user).iterator().next();
161-
}
162-
163159
/**
164-
* Traverse all secondary groups (as there could be more than one
165-
* and position is not guaranteed) and ensure there is queue with
166-
* the same name.
160+
* Sets group related data for the provided variable context.
161+
* Primary group is the first group returned by getGroups.
162+
* To determine secondary group we traverse all groups
163+
* (as there could be more than one and position is not guaranteed) and
164+
* ensure there is queue with the same name.
165+
* This method also sets the groups set for the variable context for group
166+
* matching.
167+
* @param vctx Variable context to be updated
167168
* @param user Name of the user
168-
* @return Name of the secondary group if found, null otherwise
169169
* @throws IOException
170170
*/
171-
private String getSecondaryGroup(String user) throws IOException {
171+
private void setupGroupsForVariableContext(VariableContext vctx, String user)
172+
throws IOException {
172173
Set<String> groupsSet = groups.getGroupsSet(user);
173174
String secondaryGroup = null;
174175
Iterator<String> it = groupsSet.iterator();
175-
it.next();
176+
String primaryGroup = it.next();
176177
while (it.hasNext()) {
177178
String group = it.next();
178179
if (this.queueManager.getQueue(group) != null) {
@@ -185,7 +186,10 @@ private String getSecondaryGroup(String user) throws IOException {
185186
LOG.debug("User {} is not associated with any Secondary " +
186187
"Group. Hence it may use the 'default' queue", user);
187188
}
188-
return secondaryGroup;
189+
190+
vctx.put("%primary_group", primaryGroup);
191+
vctx.put("%secondary_group", secondaryGroup);
192+
vctx.putExtraDataset("groups", groupsSet);
189193
}
190194

191195
private VariableContext createVariableContext(
@@ -195,9 +199,8 @@ private VariableContext createVariableContext(
195199
vctx.put("%user", user);
196200
vctx.put("%specified", asc.getQueue());
197201
vctx.put("%application", asc.getApplicationName());
198-
vctx.put("%primary_group", getPrimaryGroup(user));
199-
vctx.put("%secondary_group", getSecondaryGroup(user));
200202
vctx.put("%default", "root.default");
203+
setupGroupsForVariableContext(vctx, user);
201204

202205
vctx.setImmutables(immutableVariables);
203206
return vctx;

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public static MappingRule createLegacyRule(
109109
matcher = MappingRuleMatchers.createUserMatcher(source);
110110
break;
111111
case GROUP_MAPPING:
112-
matcher = MappingRuleMatchers.createGroupMatcher(source);
112+
matcher = MappingRuleMatchers.createUserGroupMatcher(source);
113113
break;
114114
case APPLICATION_MAPPING:
115115
matcher = MappingRuleMatchers.createApplicationNameMatcher(source);

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleMatchers.java

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.hadoop.yarn.server.resourcemanager.placement;
2020

2121
import java.util.Arrays;
22+
import java.util.Set;
2223

2324
/**
2425
* This class contains all the matcher and some helper methods to generate them.
@@ -96,6 +97,48 @@ public String toString() {
9697
}
9798
}
9899

100+
/**
101+
* The GroupMatcher will check if any of the user's groups match the provided
102+
* group name. It does not care if it's primary or secondary group, it just
103+
* checks if the user is member of the expected group.
104+
*/
105+
public static class UserGroupMatcher implements MappingRuleMatcher {
106+
/**
107+
* The group which should match the users's groups.
108+
*/
109+
private String group;
110+
111+
UserGroupMatcher(String value) {
112+
this.group = value;
113+
}
114+
115+
/**
116+
* The method will match (return true) if the user is in the provided group.
117+
* This matcher expect an extraVariableSet to be present in the variable
118+
* context, if it's not present, we return false.
119+
* If the expected group is null we always return false.
120+
* @param variables The variable context, which contains all the variables
121+
* @return true if user is member of the group
122+
*/
123+
@Override
124+
public boolean match(VariableContext variables) {
125+
Set<String> groups = variables.getExtraDataset("groups");
126+
127+
if (group == null || groups == null) {
128+
return false;
129+
}
130+
131+
String substituted = variables.replaceVariables(group);
132+
return groups.contains(substituted);
133+
}
134+
135+
@Override
136+
public String toString() {
137+
return "GroupMatcher{" +
138+
"group='" + group + '\'' +
139+
'}';
140+
}
141+
}
99142
/**
100143
* AndMatcher is a basic boolean matcher which takes multiple other
101144
* matcher as it's arguments, and on match it checks if all of them are true.
@@ -193,13 +236,13 @@ public static MappingRuleMatcher createUserMatcher(String userName) {
193236
}
194237

195238
/**
196-
* Convenience method to create a variable matcher which matches against the
197-
* user's primary group.
239+
* Convenience method to create a group matcher which matches against the
240+
* groups of the user.
198241
* @param groupName The groupName to be matched
199-
* @return VariableMatcher with %primary_group as the variable
242+
* @return UserGroupMatcher
200243
*/
201-
public static MappingRuleMatcher createGroupMatcher(String groupName) {
202-
return new VariableMatcher("%primary_group", groupName);
244+
public static MappingRuleMatcher createUserGroupMatcher(String groupName) {
245+
return new UserGroupMatcher(groupName);
203246
}
204247

205248
/**
@@ -215,7 +258,7 @@ public static MappingRuleMatcher createUserGroupMatcher(
215258
String userName, String groupName) {
216259
return new AndMatcher(
217260
createUserMatcher(userName),
218-
createGroupMatcher(groupName));
261+
createUserGroupMatcher(groupName));
219262
}
220263

221264
/**

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/VariableContext.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public class VariableContext {
4444
*/
4545
private Set<String> immutableNames;
4646

47+
/**
48+
* Some matchers may need to find a data in a set, which is not usable
49+
* as a variable in substitutions, this store is for those sets.
50+
*/
51+
private Map<String, Set<String>> extraDataset = new HashMap<>();
52+
4753
/**
4854
* Checks if the provided variable is immutable.
4955
* @param name Name of the variable to check
@@ -114,6 +120,31 @@ public String get(String name) {
114120
return ret == null ? "" : ret;
115121
}
116122

123+
/**
124+
* Adds a set to the context, each name can only be added once. The extra
125+
* dataset is different from the regular variables because it cannot be
126+
* referenced via tokens in the paths or any other input. However matchers
127+
* and actions can explicitly access these datasets and can make decisions
128+
* based on them.
129+
* @param name Name which can be used to reference the collection
130+
* @param set The dataset to be stored
131+
*/
132+
public void putExtraDataset(String name, Set<String> set) {
133+
if (extraDataset.containsKey(name)) {
134+
throw new IllegalStateException(
135+
"Dataset '" + name + "' is already set!");
136+
}
137+
extraDataset.put(name, set);
138+
}
139+
140+
/**
141+
* Returns the dataset referenced by the name.
142+
* @param name Name of the set to be returned.
143+
*/
144+
public Set<String> getExtraDataset(String name) {
145+
return extraDataset.get(name);
146+
}
147+
117148
/**
118149
* Check if a variable is part of the context.
119150
* @param name Name of the variable to be checked
@@ -195,5 +226,4 @@ public String replacePathVariables(String input) {
195226

196227
return String.join(".", parts);
197228
}
198-
199229
}

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestCSMappingPlacementRule.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,4 +411,37 @@ public void testAllowCreateFlag() throws IOException {
411411
engine, app, "charlie", "root.man.create");
412412

413413
}
414+
415+
private MappingRule createGroupMapping(String group, String queue) {
416+
MappingRuleMatcher matcher = MappingRuleMatchers.createUserGroupMatcher(group);
417+
MappingRuleAction action =
418+
(new MappingRuleActions.PlaceToQueueAction(queue, true))
419+
.setFallbackReject();
420+
return new MappingRule(matcher, action);
421+
}
422+
423+
@Test
424+
public void testGroupMatching() throws IOException {
425+
ArrayList<MappingRule> rules = new ArrayList<>();
426+
427+
rules.add(createGroupMapping("p_alice", "root.man.p_alice"));
428+
rules.add(createGroupMapping("developer", "root.man.developer"));
429+
430+
//everybody is in the user group, this should catch all
431+
rules.add(createGroupMapping("user", "root.man.user"));
432+
433+
CSMappingPlacementRule engine = setupEngine(true, rules);
434+
ApplicationSubmissionContext app = createApp("app");
435+
436+
assertPlace(
437+
"Alice should be placed to root.man.p_alice based on her primary group",
438+
engine, app, "alice", "root.man.p_alice");
439+
assertPlace(
440+
"Bob should be placed to root.man.developer based on his developer " +
441+
"group", engine, app, "bob", "root.man.developer");
442+
assertPlace(
443+
"Charlie should be placed to root.man.user because he is not a " +
444+
"developer nor in the p_alice group", engine, app, "charlie",
445+
"root.man.user");
446+
}
414447
}

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.junit.Assert.assertEquals;
2222
import static org.junit.Assert.assertTrue;
2323

24+
import com.google.common.collect.Sets;
2425
import org.apache.hadoop.util.StringUtils;
2526
import org.junit.Test;
2627

@@ -107,6 +108,7 @@ void evaluateLegacyStringTestcase(
107108
public void testLegacyEvaluation() {
108109
VariableContext matching = setupVariables(
109110
"bob", "developer", "users", "MR");
111+
matching.putExtraDataset("groups", Sets.newHashSet("developer"));
110112
VariableContext mismatching = setupVariables(
111113
"joe", "tester", "admins", "Spark");
112114

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleMatchers.java

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package org.apache.hadoop.yarn.server.resourcemanager.placement;
2020

21+
import com.google.common.collect.Sets;
2122
import junit.framework.TestCase;
2223
import org.junit.Test;
2324

@@ -41,19 +42,21 @@ public void testVariableMatcher() {
4142
matchingContext.put("%primary_group", "developers");
4243
matchingContext.put("%application", "TurboMR");
4344
matchingContext.put("%custom", "Matching string");
45+
matchingContext.putExtraDataset("groups", Sets.newHashSet("developers"));
4446

4547
VariableContext mismatchingContext = new VariableContext();
4648
mismatchingContext.put("%user", "dave");
4749
mismatchingContext.put("%primary_group", "testers");
4850
mismatchingContext.put("%application", "Tester APP");
4951
mismatchingContext.put("%custom", "Not matching string");
52+
mismatchingContext.putExtraDataset("groups", Sets.newHashSet("testers"));
5053

5154
VariableContext emptyContext = new VariableContext();
5255

5356
Map<String, MappingRuleMatcher> matchers = new HashMap<>();
5457
matchers.put("User matcher", MappingRuleMatchers.createUserMatcher("bob"));
5558
matchers.put("Group matcher",
56-
MappingRuleMatchers.createGroupMatcher("developers"));
59+
MappingRuleMatchers.createUserGroupMatcher("developers"));
5760
matchers.put("Application name matcher",
5861
MappingRuleMatchers.createApplicationNameMatcher("TurboMR"));
5962
matchers.put("Custom matcher",
@@ -184,16 +187,17 @@ public void testBoolOperatorMatchers() {
184187
VariableContext developerBob = new VariableContext();
185188
developerBob.put("%user", "bob");
186189
developerBob.put("%primary_group", "developers");
187-
190+
developerBob.putExtraDataset("groups", Sets.newHashSet("developers"));
188191

189192
VariableContext testerBob = new VariableContext();
190193
testerBob.put("%user", "bob");
191194
testerBob.put("%primary_group", "testers");
195+
testerBob.putExtraDataset("groups", Sets.newHashSet("testers"));
192196

193197
VariableContext testerDave = new VariableContext();
194198
testerDave.put("%user", "dave");
195199
testerDave.put("%primary_group", "testers");
196-
200+
testerDave.putExtraDataset("groups", Sets.newHashSet("testers"));
197201

198202
VariableContext accountantDave = new VariableContext();
199203
accountantDave.put("%user", "dave");
@@ -252,4 +256,56 @@ public void testToStrings() {
252256
", " + var.toString() + "]}", or.toString());
253257
}
254258

259+
@Test
260+
public void testGroupMatching() {
261+
VariableContext letterGroups = new VariableContext();
262+
letterGroups.putExtraDataset("groups", Sets.newHashSet("a", "b", "c"));
263+
264+
VariableContext numberGroups = new VariableContext();
265+
numberGroups.putExtraDataset("groups", Sets.newHashSet("1", "2", "3"));
266+
267+
VariableContext noGroups = new VariableContext();
268+
269+
MappingRuleMatcher matchA =
270+
MappingRuleMatchers.createUserGroupMatcher("a");
271+
MappingRuleMatcher matchB =
272+
MappingRuleMatchers.createUserGroupMatcher("b");
273+
MappingRuleMatcher matchC =
274+
MappingRuleMatchers.createUserGroupMatcher("c");
275+
MappingRuleMatcher match1 =
276+
MappingRuleMatchers.createUserGroupMatcher("1");
277+
MappingRuleMatcher match2 =
278+
MappingRuleMatchers.createUserGroupMatcher("2");
279+
MappingRuleMatcher match3 =
280+
MappingRuleMatchers.createUserGroupMatcher("3");
281+
MappingRuleMatcher matchNull =
282+
MappingRuleMatchers.createUserGroupMatcher(null);
283+
284+
//letter groups submission should match only the letters
285+
assertTrue(matchA.match(letterGroups));
286+
assertTrue(matchB.match(letterGroups));
287+
assertTrue(matchC.match(letterGroups));
288+
assertFalse(match1.match(letterGroups));
289+
assertFalse(match2.match(letterGroups));
290+
assertFalse(match3.match(letterGroups));
291+
assertFalse(matchNull.match(letterGroups));
292+
293+
//numeric groups submission should match only the numbers
294+
assertFalse(matchA.match(numberGroups));
295+
assertFalse(matchB.match(numberGroups));
296+
assertFalse(matchC.match(numberGroups));
297+
assertTrue(match1.match(numberGroups));
298+
assertTrue(match2.match(numberGroups));
299+
assertTrue(match3.match(numberGroups));
300+
assertFalse(matchNull.match(numberGroups));
301+
302+
//noGroups submission should not match anything
303+
assertFalse(matchA.match(noGroups));
304+
assertFalse(matchB.match(noGroups));
305+
assertFalse(matchC.match(noGroups));
306+
assertFalse(match1.match(noGroups));
307+
assertFalse(match2.match(noGroups));
308+
assertFalse(match3.match(noGroups));
309+
assertFalse(matchNull.match(noGroups));
310+
}
255311
}

0 commit comments

Comments
 (0)