Permission denied: user=root, access=WRITE, inode="/":hdfs:supergroup:drwxr-xr-xhd
問題分析
開始仔細的觀察了這個錯誤的詳細資訊,看到user=Administrator, access=WRITE。這裡的user其實是我當前系統(執行客戶端的計算機的作業系統)的使用者名稱,實際期望這裡的user=hadoop(hadoop是我的HADOOP上面的使用者名稱),但是它取的是當前的系統的使用者名稱,很明顯,如果我將當前系統的使用者名稱改為hadoop,這個肯定也是可以行得通的,但是如果後期將開發的程式碼部署到伺服器上之後,就不能方便的修改使用者,此方法明顯也不夠方便。
現在就想著Configuration這個是一個配置類,有沒有一個引數是可以在某個地方設定以哪個使用者執行呢?搜尋了半天,無果。沒有找到相關的配置引數。
最終只有繼續分析程式碼, FileSystem fs = FileSystem.get(URI.create(dest), conf);程式碼是在此處開始對HDFS進行呼叫,所以就想著將HADOOP的原始碼下下來,debug整個呼叫過程,這個user=Administator是在什麼時間賦予的值。理解了呼叫過程,還怕找不到解決問題的辦法麼?
跟蹤程式碼進入 FileSystem.get-->CACHE.get()-->Key key = new Key(uri, conf);到這裡的時候發現key值裡面已經有Administrator了,所以關鍵肯定是在new key的過程。繼續跟蹤UserGroupInformation.getCurrentUser()-->getLoginUser()-->login.login()到這一步的時候發現使用者名稱已經確定了,但是這個方法是Java的核心原始碼,是一個通用的安全認證,但對這一塊不熟悉,但是debug時看到subject裡面有NTUserPrincipal:Administator,所以就想著搜尋一下這個東西是啥,結果就找到了下面這一篇關鍵的文章:
http://www.udpwork.com/item/7047.html
在此篇文章裡面作者分析了hadoop的整個登入過程,對於我有用的是其中的這一段:
2.login.login();
這個會呼叫HadoopLoginModule的login()和commit()方法。
HadoopLoginModule的login()方法是一個空函式,只打印了一行除錯日誌 LOG.debug("hadoop login");
commit()方法負責把Principal新增到Subject中。
此時一個首要問題是username是什麼?
在使用了kerberos的情況下,從javax.security.auth.kerberos.KerberosPrincipal的例項獲取username。
在未使用kerberos的情況下,優先讀取HADOOP_USER_NAME這個系統環境變數,如果不為空,那麼拿它作username。否則,讀取HADOOP_USER_NAME這個java環境變數。否則,從com.sun.security.auth.NTUserPrincipal或者com.sun.security.auth.UnixPrincipal的例項獲取username。
如果以上嘗試都失敗,那麼丟擲異常LoginException("Can’t find user name")。
最終拿username構造org.apache.hadoop.security.User的例項新增到Subject中。
看完這一段,我明白了執行login.login的時候呼叫了hadoop裡面的HadoopLoginModule方法,而關鍵是在commit方法裡面,在這裡優先讀取HADOOP_USER_NAME系統環境變數,然後是java環境變數,如果再沒有就從NTUserPrincipal等裡面取。關鍵程式碼為:
if (!isSecurityEnabled() && (user == null)) {
String envUser = System.getenv(HADOOP_USER_NAME);
if (envUser == null) {
envUser = System.getProperty(HADOOP_USER_NAME);
}
user = envUser == null ? null : new User(envUser);
}
OK,看到這裡我的需求也就解決了,只要在系統的環境變數裡面新增HADOOP_USER_NAME=hadoop(HDFS上的有許可權的使用者,具體看自己的情況),或者在當前JDK的變數引數裡面新增HADOOP_USER_NAME這個Java變數即可。我的情況新增系統環境變數更方法。
解決辦法
最終,總結下來解決辦法大概有三種:
1、在系統的環境變數或java JVM變數裡面新增HADOOP_USER_NAME,這個值具體等於多少看自己的情況,以後會執行HADOOP上的Linux的使用者名稱。
2、在hdfs的配置檔案中,將dfs.permissions修改為False
3、使用HDFS的命令列介面修改相應目錄的許可權,hadoop fs -chmod 777 /user,後面的/user是要上傳檔案的路徑,不同的情況可能不一樣,比如要上傳的檔案路徑為hdfs://namenode/user/xxx.doc,則這樣的修改可以,如果要上傳的檔案路徑為hdfs://namenode/java/xxx.doc,則要修改的為hadoop fs -chmod 777 /java或者hadoop fs -chmod 777 /,java的那個需要先在HDFS裡面建立Java目錄,後面的這個是為根目錄調整許可權。