/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openflamingo.mapreduce.util; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FileUtil; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.DFSClient; import org.apache.hadoop.hdfs.protocol.HdfsFileStatus; import org.apache.hadoop.mapreduce.InputSplit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * HDFS Utility. * * @author Edward KIM * @author Seo Ji Hye * @since 0.1 */ public class HdfsUtils { /** * SLF4J Logging */ private static Logger logger = LoggerFactory.getLogger(HdfsUtils.class); public static final String DEFAULT_UGI = "hadoop,hadoop"; public static final String HDFS_URL_PREFIX = "hdfs://"; /** * Hadoop HDFS의 DFS Client를 생성한다. * * @param hdfsUrl HDFS URL * @return DFS Client * @throws java.io.IOException DFS Client를 생성할 수 없는 경우 */ public static DFSClient createDFSClient(String hdfsUrl) throws IOException { if (hdfsUrl == null || !hdfsUrl.startsWith("hdfs://")) { throw new IllegalArgumentException("HDFS URL이 잘못되었습니다. 요청한 HDFS URL [" + hdfsUrl + "]"); } String url = StringUtils.replace(hdfsUrl, "hdfs://", ""); String[] parts = url.split(":"); return createDFSClient(parts[0], Integer.valueOf(parts[1])); } /** * Hadoop HDFS의 DFS Client를 생성한다. * * @param namenodeIp Namenode IP * @param namenodePort Namenode Port * @return DFS Client * @throws java.io.IOException DFS Client를 생성할 수 없는 경우 */ public static DFSClient createDFSClient(String namenodeIp, int namenodePort) throws IOException { Configuration config = new Configuration(); InetSocketAddress address = new InetSocketAddress(namenodeIp, namenodePort); return new DFSClient(address, config); } /** * 지정한 경로를 삭제한다. * * @param client DFS Client * @param path 삭제할 경로 * @param recursive Recusive 적용 여부 * @return 성공시 <tt>true</tt> * @throws java.io.IOException 파일을 삭제할 수 없는 경우 */ public static boolean remove(DFSClient client, String path, boolean recursive) throws IOException { if (client.exists(path)) { logger.info("요청한 [{}] 파일이 존재하므로 삭제합니다. Recursive 여부 [{}]", path, recursive); return client.delete(path, recursive); } logger.info("요청한 [{}] 파일이 존재하지 않습니다.", path); return false; } /** * 지정한 경로의 파일 목록을 얻는다. * * @param client DFS Client * @param path 경로 * @return 파일 경로 목록 * @throws java.io.IOException HDFS IO를 처리할 수 없는 경우 */ public static List<String> list(DFSClient client, String path) throws IOException { List<String> list = new ArrayList<String>(); /* FileStatus[] statuses = client.listPaths(path); if (statuses != null) { for (FileStatus status : statuses) { if (!status.isDir()) { String fullyQualifiedHDFSFilename = path + "/" + status.getPath().getName(); list.add(fullyQualifiedHDFSFilename); } } } */ return list; } /** * DFS Client의 출력 스트립을 얻는다. * * @param client DFS Client * @param filename 파일명 * @param overwrite Overwrite 여부 * @return 출력 스트림 * @throws java.io.IOException HDFS IO를 처리할 수 없는 경우 */ public static OutputStream getOutputStream(DFSClient client, String filename, boolean overwrite) throws IOException { return client.create(filename, overwrite); } /** * DFS Client의 입력 스트립을 얻는다. * * @param client DFS Client * @param filename 파일 경로 * @return 입력 스트림 * @throws java.io.IOException HDFS IO를 처리할 수 없는 경우 */ public static InputStream getInputStream(DFSClient client, String filename) throws IOException { return client.open(filename); } /** * 출력 스트림을 종료한다. * * @param outputStream 출력 스트림 * @throws java.io.IOException 출력 스트림을 종료할 수 없는 경우 */ public static void closeOuputStream(OutputStream outputStream) throws IOException { outputStream.close(); } /** * 입력 스트림을 종료한다. * * @param inputStream 입력 스트림 * @throws java.io.IOException 입력 스트림을 종료할 수 없는 경우 */ public static void closeInputStream(InputStream inputStream) throws IOException { inputStream.close(); } /** * Input Split의 파일명을 반환한다. * Input Split은 기본적으로 <tt>file + ":" + start + "+" + length</tt> 형식으로 구성되어 있다. * * @param inputSplit Input Split * @return 파일명 */ public static String getFilename(InputSplit inputSplit) { String filename = org.openflamingo.mapreduce.util.FileUtils.getFilename(inputSplit.toString()); int start = filename.indexOf(":"); return filename.substring(0, start); } /** * @param hdfsUrl * @return * @throws java.io.IOException */ public static FileSystem getFileSystem(String hdfsUrl) throws IOException { Configuration configuration = new Configuration(); configuration.set("fs.default.name", hdfsUrl); FileSystem fileSystem = FileSystem.get(configuration); return fileSystem; } /** * 지정한 경로가 존재하는지 확인한다. * * @param client DFS Client * @param path 존재 여부를 판단할 경로 * @return 존재하면 <tt>true</tt> * @throws java.io.IOException HDFS IO를 처리할 수 없는 경우 */ public static boolean exists(DFSClient client, String path) throws IOException { return client.exists(path); } /** * 지정한 경로가 파일인지 확인한다. * * @param client DFS Client * @param path 경로 * @return 파일인 경우 <tt>true</tt> * @throws java.io.IOException HDFS IO를 처리할 수 없는 경우 */ public static boolean isFile(DFSClient client, String path) throws IOException { HdfsFileStatus status = client.getFileInfo(path); return !status.isDir(); } /** * 지정한 경로가 디렉토리인지 확인한다. * * @param fs {@link org.apache.hadoop.fs.FileSystem} * @param path 경로 * @return 디렉토리인 경우 <tt>true</tt> * @throws java.io.IOException HDFS IO를 처리할 수 없는 경우 */ public static boolean isDirectory(FileSystem fs, String path) throws IOException { try { FileStatus status = fs.getFileStatus(new Path(path)); return status.isDir(); } catch (FileNotFoundException ex) { return false; } } /** * 문자열을 지정한 파일로 저장한다. * * @param client DFS Client * @param path 저장할 파일의 절대 경로 * @param content 저장할 파일의 문자열 내용 * @throws java.io.IOException HDFS IO를 처리할 수 없는 경우 */ public static void saveFile(DFSClient client, String path, String content) throws IOException { OutputStream outputStream = getOutputStream(client, path, true); org.openflamingo.mapreduce.util.FileUtils.copy(content.getBytes(), outputStream); outputStream.close(); } /** * 지정한 경로의 파일 정보를 얻어온다. * * @param client DFS Client * @param path 파일 정보를 얻어올 경로 * @return 파일 정보 * @throws java.io.IOException HDFS IO를 처리할 수 없는 경우 */ public static HdfsFileStatus getFileInfo(DFSClient client, String path) throws IOException { return client.getFileInfo(path); } /** * 다운로드한 로컬 파일 시스템에 존재하는 파일을 지정한 HDFS에 업로드한다. * * @param hdfsUrl HDFS URL * @param filename HDFS의 Path에 저장할 파일명 * @param hdfsPath HDFS의 Path * @param downloadFilePath 로컬 파일 시스템에 있는 다운로드한 파일 * @throws java.io.IOException HDFS 작업을 실패한 경우 */ public static void uploadToHdfs(String hdfsUrl, String filename, String hdfsPath, String downloadFilePath) throws IOException { String hdfsFullPath = hdfsPath + "/" + filename; File inputFile = new File(downloadFilePath); DFSClient dfsClient = HdfsUtils.createDFSClient(hdfsUrl); copyFromLocalFileToHdfsFile(inputFile, dfsClient, hdfsFullPath); dfsClient.close(); } /** * 로컬 파일 시스템의 파일을 HDFS로 복사한다. * * @param inputFile 로컬 파일 시스템의 입력 파일 * @param client DFSClient * @param hdfsPath HDFS의 출력 파일 경로 * @throws java.io.IOException 파일을 복사할 수 없는 경우 */ public static void copyFromLocalFileToHdfsFile(File inputFile, DFSClient client, String hdfsPath) throws IOException { OutputStream outputStream = HdfsUtils.getOutputStream(client, hdfsPath, true); InputStream inputStream = new FileInputStream(inputFile); org.openflamingo.mapreduce.util.FileUtils.copy(inputStream, outputStream); } /** * HDFS 상에서 지정한 디렉토리의 파일을 다른 디렉토리로 파일을 이동시킨다. * * @param hdfsUrl HDFS URL * @param sourceDirectory 소스 디렉토리 * @param targetDirectory 목적 디렉토리 * @throws java.io.IOException 파일을 이동할 수 없는 경우 */ public static void moveFilesToDirectory(String hdfsUrl, String sourceDirectory, String targetDirectory) throws IOException { Configuration conf = new Configuration(); conf.set("fs.default.name", hdfsUrl); conf.set("hadoop.job.ugi", DEFAULT_UGI); FileSystem fileSystem = FileSystem.get(conf); FileStatus[] statuses = fileSystem.listStatus(new Path(sourceDirectory)); for (int i = 0; i < statuses.length; i++) { FileStatus fileStatus = statuses[i]; if (!isDirectory(fileSystem, targetDirectory)) { logger.info("HDFS에 [{}] 디렉토리가 존재하지 않아서 생성합니다.", targetDirectory); fileSystem.mkdirs(new Path(targetDirectory)); } fileSystem.rename(fileStatus.getPath(), new Path(targetDirectory)); logger.info("HDFS의 파일 [{}]을 [{}] 디렉토리로 이동했습니다.", fileStatus.getPath(), targetDirectory); } } /** * 디렉토리가 존재하지 않는다면 생성한다. * * @param directory 디렉토리 * @param hdfsUrl HDFS URL * @throws java.io.IOException HDFS 작업을 실패한 경우 */ public static void makeDirectoryIfNotExists(String directory, String hdfsUrl) throws IOException { Configuration conf = new Configuration(); conf.set("fs.default.name", hdfsUrl); conf.set("hadoop.job.ugi", DEFAULT_UGI); FileSystem fileSystem = FileSystem.get(conf); if (!isDirectory(fileSystem, directory)) { logger.info("HDFS에 [{}] 디렉토리가 존재하지 않아서 생성합니다.", directory); fileSystem.mkdirs(new Path(directory)); } } /** * 해당 HDFS 디렉토리에 있는 모든 파일 목록을 반환한다. * * @param hdfsUrl HDFS URL * @param hdfsDirectories HDFS 디렉토리 목록 * @return HDFS 디렉토리에 포함되어 있는 모든 파일 목록 * @throws java.io.IOException HDFS에 접근할 수 없거나, 파일 목록을 알아낼 수 없는 경우 */ public static String[] getHdfsFiles(String hdfsUrl, List<String> hdfsDirectories) throws IOException { List<String> filesInDirectories = new ArrayList<String>(); DFSClient client = HdfsUtils.createDFSClient(hdfsUrl); for (Iterator<String> stringIterator = hdfsDirectories.iterator(); stringIterator.hasNext(); ) { String hdfsDirectory = stringIterator.next(); List<String> files = HdfsUtils.list(client, hdfsDirectory); filesInDirectories.addAll(files); } client.close(); return StringUtils.toStringArray(filesInDirectories); } /** * HDFS의 해당 경로의 모든 파일에서 지정한 확장자를 가진 파일 목록을 반환한다. * * @param hdfsUrl HDFS URL * @param ext 확장자(예: <tt>.dat</tt>) * @param path 경로 * @return "<tt>.dat</tt>" 확장자를 가진 파일 목록 * @throws java.io.IOException HDFS 작업을 실패한 경우 */ public static String[] getHdfsFiles(String hdfsUrl, String ext, String path) throws IOException { ArrayList<String> files = new ArrayList<String>(); DFSClient client = HdfsUtils.createDFSClient(hdfsUrl); makeDirectoryIfNotExists(path, hdfsUrl); /* FileStatus[] statuses = client.listPaths(path); if (statuses != null) { for (int index = 0; index < statuses.length; index++) { FileStatus file = statuses[index]; if (!file.isDir() && file.getPath().getName().endsWith(ext)) { logger.debug("\tHDFS의 [{}] 디렉토리에는 [{}] 파일이 있습니다.", path, file.getPath().getName()); files.add(file.getPath().getName()); } } } */ client.close(); return StringUtils.toStringArray(files); } /** * 지정한 경로에 파일이 존재하는지 확인한다. * * @param hdfsUrl HDFS URL * @param path 존재 여부를 확인할 절대 경로 * @return 존재한다면 <tt>true</tt> * @throws java.io.IOException 파일 존재 여부를 알 수 없거나, HDFS에 접근할 수 없는 경우 */ public static boolean isExist(String hdfsUrl, String path) throws IOException { DFSClient client = HdfsUtils.createDFSClient(hdfsUrl); HdfsFileStatus status = client.getFileInfo(path); if (status != null && !status.isDir()) { logger.info("파일 [{}{}]이 HDFS에 존재합니다.", hdfsUrl, path); client.close(); return true; } logger.info("파일 [{}{}]이 HDFS이 존재하지 않습니다.", hdfsUrl, path); client.close(); return false; } /** * HDFS에서 지정한 디렉토리의 모든 파일을 삭제한다. * * @param hdfsUrl HDFS URL * @param hdfsDirectory 파일을 삭제할 HDFS Directory URL * @throws java.io.IOException 파일을 삭제할 수 없는 경우 */ public static void deleteFromHdfs(String hdfsUrl, String hdfsDirectory) throws IOException { Configuration conf = new Configuration(); conf.set("fs.default.name", hdfsUrl); FileSystem fs = FileSystem.get(conf); FileStatus[] statuses = fs.globStatus(new Path(hdfsDirectory)); for (int i = 0; i < statuses.length; i++) { FileStatus fileStatus = statuses[i]; fs.delete(fileStatus.getPath(), true); } } /** * 해당 경로에 있는 파일을 MERGE한다. * * @param hdfsUrl HDFS URL * @param path HDFS Path * @throws java.io.IOException Get Merge할 수 없는 경우 */ public static void merge(String hdfsUrl, String path) throws IOException { // 입력 경로의 모든 파일을 Get Merge하여 임시 파일에 기록한다. Configuration conf = new Configuration(); conf.set("fs.default.name", hdfsUrl); FileSystem fileSystem = FileSystem.get(conf); Path source = new Path(path); if (!fileSystem.getFileStatus(source).isDir()) { // 이미 파일이라면 더이상 Get Merge할 필요없다. return; } Path target = new Path(path + "_temporary"); FileUtil.copyMerge(fileSystem, source, fileSystem, target, true, conf, null); // 원 소스 파일을 삭제한다. fileSystem.delete(source, true); // 임시 파일을 원 소스 파일명으로 대체한다. Path in = new Path(path + "_temporary"); Path out = new Path(path); fileSystem.rename(in, out); // 임시 디렉토리를 삭제한다. fileSystem.delete(new Path(path + "_temporary"), true); } }