实现hive proxy5-数据目录权限问题解决

hive创建目录时相关的几个hdfs中的类:

org.apache.hadoop.hdfs.DistributedFileSystem,FileSystem 的具体实现类
org.apache.hadoop.hdfs.DFSClient,client操作hdfs文件系统的类
org.apache.hadoop.fs.permission.FsPermission 文件权限相关类,主要的方法有getUMask和applyUMask方法

org.apache.hadoop.hdfs.DistributedFileSystem中需要注意的几个方法:
initialize,主要用来初始DFSClient的实例:

  @Override
  public void initialize(URI uri, Configuration conf) throws IOException {
    super.initialize(uri, conf);
    setConf(conf);
    String host = uri.getHost();
    if (host == null) {
      throw new IOException("Incomplete HDFS URI, no host: "+ uri);
    }
    this.dfs = new DFSClient(uri, conf, statistics);
    this.uri = URI.create(uri.getScheme()+"://"+uri.getAuthority());
    this.workingDir = getHomeDirectory();
  }

mkdir用来创建一个目录,mkdirs用来创建多个目录(类似于mkdir -p):

  public boolean mkdir(Path f, FsPermission permission) throws IOException {
    statistics.incrementWriteOps(1);
    return dfs.mkdirs(getPathName(f), permission, false);
  }
  public boolean mkdirs(Path f, FsPermission permission) throws IOException {
    statistics.incrementWriteOps(1);
    return dfs.mkdirs(getPathName(f), permission, true);
  }

两者最终调用的都是DFSClient.mkdirs方法,org.apache.hadoop.hdfs.DFSClient的mkdirs方法:

final Conf dfsClientConf;
...
  public boolean mkdirs(String src, FsPermission permission,
      boolean createParent) throws IOException {
    if (permission == null) { //如果传入的权限为null
      permission = FsPermission.getDefault();
    }
    FsPermission masked = permission.applyUMask(dfsClientConf.uMask);
    return primitiveMkdir(src, masked, createParent); //调用primitiveMkdir方法    
  }

这里需要注意 FsPermission.getDefault方法和Conf.uMask属性(Conf是DFSClient的内部类,主要用来设置默认配置)
Conf.uMask属性:

uMask = FsPermission.getUMask(conf); //由getUMask获取

getUMask方法:

 public static final String DEPRECATED_UMASK_LABEL = "dfs.umask";
  public static final String UMASK_LABEL =
                  CommonConfigurationKeys.FS_PERMISSIONS_UMASK_KEY;  //fs.permissions.umask-mode
  public static final int DEFAULT_UMASK =
                  CommonConfigurationKeys.FS_PERMISSIONS_UMASK_DEFAULT; //0022
                     
  public static FsPermission getUMask(Configuration conf) {
    int umask = DEFAULT_UMASK;
    if(conf != null) {
      String confUmask = conf.get(UMASK_LABEL);
      int oldUmask = conf.getInt(DEPRECATED_UMASK_LABEL, Integer.MIN_VALUE); //老的配置项:dfs.umask,默认值为Integer.MIN_VALUE(-2147483648)
      try {
        if(confUmask != null) { //如果设置了fs.permissions.umask-mode,则按这个umask,否则为默认的umask(0022)
          umask = new UmaskParser(confUmask).getUMask();
        }
      } catch(IllegalArgumentException iae) {
        // Provide more explanation for user-facing message
        String type = iae instanceof NumberFormatException ? "decimal"
            : "octal or symbolic";
        String error = "Unable to parse configuration " + UMASK_LABEL
            + " with value " + confUmask + " as " + type + " umask.";
        LOG.warn(error);
       
        // If oldUmask is not set, then throw the exception
        if (oldUmask == Integer.MIN_VALUE) {
          throw new IllegalArgumentException(error);
        }
      }
       
      if(oldUmask != Integer.MIN_VALUE) { //如果手动设置了老的配置项dfs.umask
        if (umask != oldUmask) { //并且dfs.umask的值不等于0022
          LOG.warn(DEPRECATED_UMASK_LABEL
              + " configuration key is deprecated. " + "Convert to "
              + UMASK_LABEL + ", using octal or symbolic umask "
              + "specifications.");
          // Old and new umask values do not match - Use old umask
          umask = oldUmask; //umask为默认值0022
        }
      }
    }
   
    return new FsPermission((short)umask);
  }

在hive中创建hdfs的目录有两种方法
1)通过Utilities的createDirsWithPermission方法,这种方法会重设fs.permissions.umask-mode
2)直接通过DistributedFileSystem的mkdirs方法创建
两者最终都是调用了DFSClient的mkdirs方法,不同的是调用Utilities.createDirsWithPermission创建的目录权限在proxy时权限有可能是777(因为手动设置了权限为777),
比如:
Context类的构造函数中创建临时文件目录通过Context.getMRScratchDir调getLocalScratchDir(local job)或getScratchDir(非local job),其中getScratchDir中调用Utilities.createDirsWithPermission方法调用目录

public static boolean createDirsWithPermission(Configuration conf, Path mkdirPath,
    FsPermission fsPermission, boolean recursive) throws IOException {
  String origUmask = null;
  LOG.warn("Create dirs " + mkdirPath + " with permission " + fsPermission + " recursive " +
      recursive);
  if (recursive) {
  //如果recursive为true,设置fs.permissions.umask-mode为000,
  //默认情况下recursive = SessionState.get().isHiveServerQuery() &&conf.getBoolean(HiveConf.ConfVars.HIVE_SERVER2_ENABLE_DOAS.varname,HiveConf.ConfVars.HIVE_SERVER2_ENABLE_DOAS.defaultBoolVal);
  //即时来自hiveserver的请求,并且开启了doas,这里还会把权限设置为777(这里我增加了一个逻辑,如果设置了proxy,recursive也为true)
  /**
  boolean recursive = false;
  if (SessionState.get() != null) {
    recursive = (SessionState.get().isHiveServerQuery() &&
        conf.getBoolean(HiveConf.ConfVars.HIVE_SERVER2_ENABLE_DOAS.varname,
            HiveConf.ConfVars.HIVE_SERVER2_ENABLE_DOAS.defaultBoolVal))||(HiveConf.getBoolVar(conf,HiveConf.ConfVars.HIVE_USE_CUSTOM_PROXY));
    fsPermission = new FsPermission((short)00777);
  }
  */
    origUmask = conf.get("fs.permissions.umask-mode");
    conf.set("fs.permissions.umask-mode", "000");
  }
  FileSystem fs = ShimLoader.getHadoopShims().getNonCachedFileSystem(mkdirPath.toUri(), conf);
  //这里是DFSClient的实例
  boolean retval = false;
  try {
    retval = fs.mkdirs(mkdirPath, fsPermission);
    resetConfAndCloseFS(conf, recursive, origUmask, fs);
  } catch (IOException ioe) {
    try {
      resetConfAndCloseFS(conf, recursive, origUmask, fs); //调用resetConfAndCloseFS,reset fs.permissions.umask-mode的设置
    }
    catch (IOException e) {
      // do nothing - double failure
    }
  }
  return retval;
}

resetConfAndCloseFS方法用来重设fs.permissions.umask-mode的设置,这样如果后面创建目录不是使用Utilities.createDirsWithPermission就会使用这个重设的配置

private static void resetConfAndCloseFS (Configuration conf, boolean unsetUmask,
    String origUmask, FileSystem fs) throws IOException {
  if (unsetUmask) { //unsetUmask为true,即recursive为true的话,需要重设fs.permissions.umask-mode
    if (origUmask != null) { //如果有设置项的话,使用设置项
      conf.set("fs.permissions.umask-mode", origUmask);
    } else {
      conf.unset("fs.permissions.umask-mode"); //这里虽然可以unset,后面会有默认值
    }
  }
  fs.close();
}

通过查看DFSClient的源码,发现在DFSClient的构造函数中会初始化ugi的信息,默认为当前用户

final UserGroupInformation ugi;
...
this.ugi = UserGroupInformation.getCurrentUser(); 
如果更改成proxy用户,通过运行hadoop fs -mkdir测试,发现生成的文件目录属主还是当前登录用户
更改DFSClient的构造方法:
//this.ugi = UserGroupInformation.getCurrentUser();
if(conf.getBoolean("use.custom.proxy",false)){
  this.ugi = UserGroupInformation.createRemoteUser(conf.get("custom.proxy.user"));
}else{
  this.ugi = UserGroupInformation.getCurrentUser();
}

在hdfs-site.xml配置中增加:
dfs配置中增加:

<property>
    <name>use.custom.proxy</name>        
    <value>true</value>
</property>
<property>
    <name>custom.proxy.user</name>       
    <value>ericni</value>
</property>

使用hdfs创建目录后,目录的属主仍然是hdfs,而数据写入的用户为提交job的用户。
因为上面的原因,要想使创建的hdfs的目录属主为proxy的用户,可以采用创建完后设置owner的方法。
通过查看DistributedFileSystem类的api,发现有setOwner的方法。
以insert overwrite 语句为例,在mapred job提交之前,会根据job的上下文内容,创建map和reduce的临时目录,这个目录是最终数据落地的目录,落地之后,在job完成的finally阶段,会通过MoveTask移动到对应的目录下面临时数据写入目录在ExecDriver类的execute方法中生成:

public int execute(DriverContext driverContext) {
  IOPrepareCache ioPrepareCache = IOPrepareCache.get();
  ioPrepareCache.clear();
  boolean success = true;
  Context ctx = driverContext.getCtx();
  boolean ctxCreated = false;
  Path emptyScratchDir;
  MapWork mWork = work.getMapWork();
  ReduceWork rWork = work.getReduceWork();
  try {
    if (ctx == null) {
      ctx = new Context(job);
      ctxCreated = true;
    }
    emptyScratchDir = ctx.getMRTmpPath();
    FileSystem fs = emptyScratchDir.getFileSystem(job);
    fs.mkdirs(emptyScratchDir);
  } catch (IOException e) {
    e.printStackTrace();
    console.printError("Error launching map-reduce job", "\n"
        + org.apache.hadoop.util.StringUtils.stringifyException(e));
    return 5;
  }
....
  List<Path> inputPaths = Utilities.getInputPaths(job, mWork, emptyScratchDir, ctx);  //获取输入目录
  Utilities.setInputPaths(job, inputPaths);
  Utilities.setMapRedWork(job, work, ctx.getMRTmpPath());
....
  Utilities.createTmpDirs(job, mWork); //创建map临时目录
  Utilities.createTmpDirs(job, rWork); //创建reduce临时目录
一种思路,在外层创建目录后setOwner,可以在Utilities中增加一个方法调用setOwner:
public static void setDirWithOwner(Configuration conf,Path mkdirPath,
        String username,String groupname) throws  IOException {
   LOG.warn("in Utilities setDirWithOwner path: " + mkdirPath + ",username: " + username + ",groupname: " + groupname);
   FileSystem fs = ShimLoader.getHadoopShims().getNonCachedFileSystem(mkdirPath.toUri(), conf);
   try {
      fs.setOwner(mkdirPath, username, groupname); //调用DistributedFileSystem.setOwner方法
   }catch (IOException ios) {
     //no-op
   } 
}

同时更改createTmpDirs方法:

  private static void createTmpDirs(Configuration conf,
      List<Operator<? extends OperatorDesc>> ops) throws IOException {
    FsPermission fsPermission = new FsPermission((short)00777);
    while (!ops.isEmpty()) {
      Operator<? extends OperatorDesc> op = ops.remove(0);
      if (op instanceof FileSinkOperator) {
        FileSinkDesc fdesc = ((FileSinkOperator) op).getConf(); //org.apache.hadoop.hive.ql.plan.FileSinkDesc
        Path tempDir = fdesc.getDirName(); //获取目录名
        if (tempDir != null) {
          Path tempPath = Utilities.toTempPath(tempDir);  //目录增加_tmp.前缀
          createDirsWithPermission(conf, tempPath, fsPermission);
            if (conf.getBoolean("use.custom.proxy",false)) { //如果设置了use.custom.proxy,则调用setDirWithOwner方法,设置目录权限
          LOG.warn("set owner after create dirs");
          String username = conf.get("custom.proxy.user");
          setDirWithOwner(conf,tempPath,username,null);
        }
        }
      }
      if (op.getChildOperators() != null) {
        ops.addAll(op.getChildOperators());
      }
    }
  }

上面这种方法有一定的局限性,比如是使用了Utilities.createTmpDirs的方法创建的目录才有用(比如map或者reduce的临时数据目录)。
可以通过改下层的实现:
在DFSClient中增加一个setOwner方法:

public boolean setOwner(String src, String username) throws IOException {
    boolean setResult = false;
    checkOpen();
    try {
        namenode.setOwner(src, username, null);
        setResult = true;
    } catch(RemoteException re) {
        throw re.unwrapRemoteException(AccessControlException.class,
                                       FileNotFoundException.class,
                                       SafeModeException.class,
                                       UnresolvedPathException.class);
   }finally{
        return setResult;
   }
}

同时更改primitiveMkdir为如下内容:

public boolean primitiveMkdir(String src, FsPermission absPermission,
  boolean createParent)
  throws IOException {
  checkOpen();
  boolean MkRe;
  boolean SetRe;
  if (absPermission == null) {
    absPermission =
      FsPermission.getDefault().applyUMask(dfsClientConf.uMask);
  }
  if(LOG.isDebugEnabled()) {
    LOG.debug(src + ": masked=" + absPermission);
  }
  try {
      MkRe = namenode.mkdirs(src, absPermission, createParent); //namenode:org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB
      if (this.conf.getBoolean("use.custom.proxy",false)){
          LOG.warn("change primitiveMkdir add conf: " + this.conf.getBoolean("use.custom.proxy",false));
          LOG.warn("change primitiveMkdir add conf: " + this.conf.get("custom.proxy.user"));
          String username = this.conf.get("custom.proxy.user");
            if (("").equals(username)||username == null||("hdfs").equals(username)){
                //no-op
                 SetRe = true;
            }else{
                 SetRe = setOwner(src,username);
            }      
      }else {
          SetRe = true;
      }
      return MkRe&&SetRe;
  } catch(RemoteException re) {
    throw re.unwrapRemoteException(AccessControlException.class,
                                   InvalidPathException.class,
                                   FileAlreadyExistsException.class,
                                   FileNotFoundException.class,
                                   ParentNotDirectoryException.class,
                                   SafeModeException.class,
                                   NSQuotaExceededException.class,
                                   DSQuotaExceededException.class,
                                   UnresolvedPathException.class);
  }
}

这样,只要是调用了DFSClient的primitiveMkdir方法创建的目录(正常情况下创建目录都会调用primitiveMkdir方法),在proxy的情况下都可以更改目录。

到这里,hive的proxy算是开发完成了,为了实现proxy的功能,对hive和hadoop的代码更改如下:

1.HiveConf中增加两个配置项
2.重写HadoopDefaultAuthenticator的setConf方法
3.更改Context构造方法中关于scratch目录的项
4.更改Utilities中的createDirsWithPermission方法和createTmpDirs方法,并新增setDirWithOwner方法
5.更改HiveHistoryImpl构造方法中关于日志路径的项
6.更改JobClient的init方法
7.更改DFSClient的构造方法,增加一个setOwner方法,同时更改primitiveMkdir方法

时间: 2024-10-10 14:44:53

实现hive proxy5-数据目录权限问题解决的相关文章

实现hive proxy4-scratch目录权限问题解决

hive在hdfs中的job中间文件是根据当前登陆用户产生的,其默认值为/tmp/hive-${user.name},这就导致实现proxy的功能时会遇到临时文件的权限问题,比如在实现了proxy功能后,以超级用户hdfs proxy到普通用户user时,在hdfs中的临时文件在/tmp/hive-user目录中,而目录的属主是hdfs,这时再以普通用户user运行job时,对这个目录就会有权限问题,下面说下这里proxy的实现和解决权限问题的方法:1.实现proxy功能更改org.apache

0015-如何使用Sentry管理Hive外部表权限

温馨提示:要看高清无码套图,请使用手机打开并单击图片放大查看. 1.文档编写目的 本文档主要讲述如何使用Sentry对Hive外部表权限管理,并基于以下假设: 1.操作系统版本:RedHat6.5 2.CM版本:CM 5.11.1 3.集群已启用Kerberos和Sentry 4.采用具有sudo权限的ec2-user用户进行操作 2.前置准备 2.1创建外部表数据父目录 1.使用hive用户登录Kerberos [[email protected] 1874-hive-HIVESERVER2]

mac下修改mysql-root密码-各种权限问题解决

官方资料:http://dev.mysql.com/doc/refman/5.0/en/resetting-permissions.html#resetting-permissions-unix 还有一个值得参考的mysql安装,与python-mysql安装博客http://hearrain.com/2011/01/498 据官方文档说, For example, if you run the server using the mysql login account, you should l

virtualbox共享文件夹无访问权限问题解决方法

早就困扰了,这次新装虚拟机又碰到了,记录下来. 这篇文章主要介绍了virtualbox共享文件夹无访问权限问题解决方法,造成这个问题的原因是不跟virtualbox在同一个用户组,所以加入同个组即可解决这个问题,需要的朋友可以参考下virtualbox的共享文件夹一般都挂载在/media下面,用ll查看会发现文件夹的所有者是root,所有组是vboxsf,所以文件管理去无法访问是正常的,解决方法是把你自己加入到vboxsf组里面. 复制代码代码如下:sudo usermod -a -G vbox

实现hive proxy3-日志目录权限问题解决

使用proxy之后,目录名为proxy之后的用户名目录,但是生成的文件属主是当前登陆用户,导致不能正常写入,日志目录的创建在org.apache.hadoop.hive.ql.history.HiveHistoryImpl类中,更改后的构造方法(增加了proxy之后的代码): public HiveHistoryImpl(SessionState ss) {   try {     console = new LogHelper(LOG);     if(ss.getConf().getBool

【转载】在 Mac OS X El Capitan 文件权限问题解决方法 (以安装 IPython 和 XtraFinder为例)

转载者注:升级了EI Captitan后,Mac系统对很多文件的管理权限直接进行了锁死,root无法修改,目前据我所知受影响的包括vim的配置文件,Python的一些文件(Python转exe程序的工具也会出问题),本篇文章提供了一个很好的思路,我也是在安装ipython时搜到这篇文章的. 亲测原文内容可用,但是貌似还少了一个步骤,因为我完全按照原文内容安装后ipython找不到,还要在pip安装并更新后运行easy_install,这样的话可以将ipython加入/usr/bin/的路径中.

备忘 Linux下非root用户实现crontab+rsync数据同步权限问题解决办法

如果在命令行手动执行rsync命令可以正常同步数据,但是在crontab定时任务里提示权限失败. 遇到这种情况,可以在rysnc命令里指定用ssh安全隧道方式的同时参数指定使用可以免密码登录对方机器的认证密钥文件. 1,创建一个新的密钥 ssh-keygen -t rsa 2,将密钥添加到对方主机信任中,实现免密码ssh登录 ssh-copy-id -i[密钥文件] [非root用户名]@[对方主机] 3,再在crontab里跑rsync试试 rsync -e'ssh -p22 -i[你的密钥文

MQ7.5以后的权限问题解决

MQ7.5以后权限是个问题,目前我也没有什么特别好的解决办法,把认证通道关闭就可以正常使用. 下面是IBM 官方的解释,可惜我没调通,望高人指点! 疑问 您使用MQ 7.1或者7.5创建了一个新的队列管理器.然后,您尝试使用管理员账户从客户端访问该队列管理器.您得到返回值为“2035 MQRC_NOT_AUTHORIZED”的错误. 为什么在MQ 6.x或者7.0.x中,MQ管理员可以远程访问队列管理器,而不会产生任何错误? 原因 当您使用MQ 7.1或者MQ 7.5创建一个新的队列管理器的时候

Ubuntu下使用git clone 的权限问题解决方法

问题1.sign_and_send_pubkey: signing failed: agent refused operation,执行如下语句: 1 eval "$(ssh-agent -s)" 2 ssh-add 问题2.若出现如下提示: 则对应修改自己的私钥文件权限即可. 1 chmod 700 parkcloud-new.pem 然后重新执行git clone 远程仓库地址即可. 原文地址:https://www.cnblogs.com/hsl-shiliang/p/91730