UserGroupInformation類定義了一個與檔案系統相關的使用者群組資訊抽象的内容,Hadoop架構實作了一個基于Unix系統的使用者群組資訊的實作類UnixUserGroupInformation,該類繼承自UserGroupInformation抽象類。
從UserGroupInformation抽象類與其子類UnixUserGroupInformation的屬性字段可以看出,抽象類所定義的功能資訊重心在,描述一個登入以後獲得的UserGroupInformation執行個體,而UnixUserGroupInformation類主要是側重于登入前的資訊的處理。首先看一下UnixUserGroupInformation類中定義的屬性:
public static final String DEFAULT_USERNAME = "DrWho";
public static final String DEFAULT_GROUP = "Tardis";
final static private HashMap<String, UnixUserGroupInformation> user2UGIMap = new HashMap<String, UnixUserGroupInformation>();
private String userName;
private String[] groupNames;
final private static String UGI_TECHNOLOGY = "STRING_UGI";
前面兩個是預設的Unix使用者名DEFAULT_USERNAME群組名DEFAULT_GROUP,另外其中使用者名userName群組名groupNames是根據UnixUserGroupInformation類構造方法設定的,這樣保證了即使無法得到使用者群組的資訊,也能夠使用預設的值去填充,比較适合用于測試,快速定位到使用者名群組名的設定處。
第二個屬性user2UGIMap是一個<使用者名, 使用者群組資訊執行個體>的Map,用來快速擷取到使用者群組的資訊。
最後一個UGI_TECHNOLOGY定義讀擷取使用者群組資訊的方式,顯然該類中預設使用從文本中讀取字元串的方式來構造。
對于UnixUserGroupInformation類執行個體的構造,該類給出了四個方法:
public UnixUserGroupInformation() {
}
public UnixUserGroupInformation(String userName, String[] groupNames) {
setUserGroupNames(userName, groupNames);
}
/**
* 根據一個或多個包含了“使用者名群組名”的字元串的來構造UnixUserGroupInformation執行個體。
*/
public UnixUserGroupInformation(String[] ugi) {
if (ugi==null || ugi.length < 2) {
throw new IllegalArgumentException( "Parameter does contain at least "+ "one user name and one group name");
}
public static UnixUserGroupInformation createImmutable(String[] ugi) {
return new UnixUserGroupInformation(ugi) {
public void readFields(DataInput in) throws IOException {
throw new UnsupportedOperationException();
}
};
}
String[] groupNames = new String[ugi.length-1];
System.arraycopy(ugi, 1, groupNames, 0, groupNames.length);
setUserGroupNames(ugi[0], groupNames);
}
該類實作了其抽象基類定義的兩個抽象方法,用來擷取使用者名群組名,如下所示:
public String[] getGroupNames() {
return groupNames;
}
public String getUserName() {
return userName;
}
UserGroupInformation抽象類實作了org.apache.hadoop.io.Writable接口,并沒有在其中實作序列化操作的兩個方法,是以在其子類UnixUserGroupInformationg給出了實作,如下:
/**
* 反序列化重組(this)對象
*/
public void readFields(DataInput in) throws IOException {
// 首先讀取UGI類型,預設就是從文本讀字元串的類型
String ugiType = Text.readString(in);
if (!UGI_TECHNOLOGY.equals(ugiType)) {
throw new IOException("Expect UGI prefix: " + UGI_TECHNOLOGY + ", but receive a prefix: " + ugiType);
}
// 從DataInput in流對象中讀取使用者群組的資訊
userName = Text.readString(in);
int numOfGroups = WritableUtils.readVInt(in);
groupNames = new String[numOfGroups];
for (int i = 0; i < numOfGroups; i++) {
groupNames[i] = Text.readString(in);
}
}
/**
* 序列化(this)對象,寫入到DataOutput out輸出流中
*/
public void write(DataOutput out) throws IOException {
// 将UGI_TECHNOLOGY寫入到DataOutput out中,寫入位置是位元組流的最前面
Text.writeString(out, UGI_TECHNOLOGY);
// 寫入操作
Text.writeString(out, userName);
WritableUtils.writeVInt(out, groupNames.length);
for (String groupName : groupNames) {
Text.writeString(out, groupName);
}
}
下面兩個方法,将使用者群組的資訊以字元串的形式存到Hadoop的配置類Configuration執行個體中,也能從一個給定的Configuration類執行個體中讀取出來:
public static void saveToConf(Configuration conf, String attr, UnixUserGroupInformation ugi ) {
conf.set(attr, ugi.toString()); // 通過鍵值對的形式存儲
}
public static UnixUserGroupInformation readFromConf(Configuration conf, String attr) throws LoginException {
String[] ugi = conf.getStrings(attr); // 從conf中讀取使用者群組資訊字元串
if(ugi == null) {
return null;
}
UnixUserGroupInformation currentUGI = null;
if (ugi.length>0 ){
currentUGI = user2UGIMap.get(ugi[0]); // 根據從配置執行個體中讀取到的使用者名,從user2UGIMap中擷取一個UnixUserGroupInformation執行個體
}
if (currentUGI == null) {
try {
currentUGI = new UnixUserGroupInformation(ugi); // 如果user2UGIMap中不存在從conf中讀取使用者對應的UnixUserGroupInformation執行個體,直接根據字元串資訊構造一個
user2UGIMap.put(currentUGI.getUserName(), currentUGI); // 同時加入到user2UGIMap中
} catch (IllegalArgumentException e) {
throw new LoginException("Login failed: "+e.getMessage());
}
}
return currentUGI;
}
與執行Unix系統的Shell指令相關的三個方法,介紹如下:
/**
* 模拟Unix系統的Shell指令whoami來擷取目前使用者名
*/
static String getUnixUserName() throws IOException {
String[] result = executeShellCommand(new String[]{Shell.USER_NAME_COMMAND}); // 調用executeShellCommand()方法
if (result.length!=1) {
throw new IOException("Expect one token as the result of " +
Shell.USER_NAME_COMMAND + ": " + toString(result));
}
return result[0];
}
/**
* 模拟Unix系統的Shell指令groups來擷取和目前使用者相關的組清單資訊
*/
private static String[] getUnixGroups() throws IOException {
return executeShellCommand(Shell.getGROUPS_COMMAND());
}
/* 根據輸入的Unix系統Shell指令,模拟Unix系統執行 */
private static String[] executeShellCommand(String[] command) throws IOException {
String groups = Shell.execCommand(command);
StringTokenizer tokenizer = new StringTokenizer(groups);
int numOfTokens = tokenizer.countTokens();
String[] tokens = new String[numOfTokens];
for (int i=0; tokenizer.hasMoreTokens(); i++) {
tokens[i] = tokenizer.nextToken();
}
return tokens;
}
上面三個方法能夠模拟執行Unix系統的Shell指令,與Hadoop架構中實作的工具類org.apache.hadoop.util.Shell類密切相關,執行Unix系統的Shell指令的實作有一點點複雜,可以參考其工具類實作。
上面分析與登入系統之前使用者群組資訊的擷取實作。當一個使用者具備了充分的資訊,可以登入檔案系統進行特定的操作。下面就分析執行登入的過程了,有三個實作方法,基本原理都是一緻的,下面給出這三個方法的聲明及其說明: /**
* 通過模拟Unix系統執行Shell指令擷取使用者名群組名資訊,執行登入動作。
*/
public static UnixUserGroupInformation login() throws LoginException;
/**
* 調用readFromConf方法從conf中擷取UGI;
* 若不為UGI != null,則根據save來決定是否将UGI存儲到conf中;
* 若UGI == null,調用無參Login()方法登入,傳回不為null的UGI,并根據save來決定是否将UGI存儲到conf中。
*/
public static UnixUserGroupInformation login(Configuration conf, boolean save) throws LoginException;
/**
* 設定save = false,調用login(conf, save)執行登入動作,也就是不把使用者群組資訊儲存到conf中。
*/
public static UnixUserGroupInformation login(Configuration conf) throws LoginException;
最後,該類還有一個用于比較兩個UGI是否相同的方法public boolean equals(Object other)。
總結一下,UnixUserGroupInformation類主要對登入前的使用者名群組名資訊進行格式化,使用兩種方式來擷取:
1、通過從Hadoop的配置類執行個體中擷取到使用者名群組名;
2、模拟執行Unix系統Shell指令擷取到 使用者名群組名。