/** * Copyright (c) 2010 Yahoo! Inc. All rights reserved. * Licensed 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. See accompanying LICENSE file. */ package org.apache.oozie.action.hadoop; import java.io.IOException; import java.net.URISyntaxException; import java.util.List; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.oozie.action.ActionExecutor; import org.apache.oozie.action.ActionExecutorException; import org.apache.oozie.client.WorkflowAction; import org.apache.oozie.service.HadoopAccessorException; import org.apache.oozie.service.HadoopAccessorService; import org.apache.oozie.service.Services; import org.apache.oozie.util.XmlUtils; import org.jdom.Element; /** * File system action executor. <p/> This executes the file system mkdir, move and delete commands */ public class FsActionExecutor extends ActionExecutor { public FsActionExecutor() { super("fs"); } Path getPath(Element element, String attribute) { String str = element.getAttributeValue(attribute).trim(); return new Path(str); } void validatePath(Path path, boolean withScheme) throws ActionExecutorException { String scheme = path.toUri().getScheme(); if (withScheme) { if (scheme == null) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS001", "Missing scheme in path [{0}]", path); } else { if (!scheme.equals("hdfs")) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS002", "Scheme [{0}] not support in path [{1}]", scheme, path); } } } else { if (scheme != null) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS003", "Scheme [{0}] not allowed in path [{1}]", scheme, path); } } } @SuppressWarnings("unchecked") void doOperations(Context context, Element element) throws ActionExecutorException { try { FileSystem fs = context.getAppFileSystem(); boolean recovery = fs.exists(getRecoveryPath(context)); if (!recovery) { fs.mkdirs(getRecoveryPath(context)); } for (Element commandElement : (List<Element>) element.getChildren()) { String command = commandElement.getName(); if (command.equals("mkdir")) { Path path = getPath(commandElement, "path"); mkdir(context, path); } else { if (command.equals("delete")) { Path path = getPath(commandElement, "path"); delete(context, path); } else { if (command.equals("move")) { Path source = getPath(commandElement, "source"); Path target = getPath(commandElement, "target"); move(context, source, target, recovery); } else { if (command.equals("chmod")) { Path path = getPath(commandElement, "path"); String str = commandElement.getAttributeValue("dir-files"); boolean dirFiles = (str == null) || Boolean.parseBoolean(str); String permissionsMask = commandElement.getAttributeValue("permissions").trim(); chmod(context, path, permissionsMask, dirFiles); } } } } } } catch (Exception ex) { throw convertException(ex); } } /** * @param path * @param context * @return FileSystem * @throws HadoopAccessorException */ private FileSystem getFileSystemFor(Path path, Context context) throws HadoopAccessorException { String user = context.getWorkflow().getUser(); String group = context.getWorkflow().getGroup(); return Services.get().get(HadoopAccessorService.class).createFileSystem(user, group, path.toUri(), new Configuration()); } /** * @param path * @param user * @param group * @return FileSystem * @throws HadoopAccessorException */ private FileSystem getFileSystemFor(Path path, String user, String group) throws HadoopAccessorException { return Services.get().get(HadoopAccessorService.class).createFileSystem(user, group, path.toUri(), new Configuration()); } void mkdir(Context context, Path path) throws ActionExecutorException { try { validatePath(path, true); FileSystem fs = getFileSystemFor(path, context); if (!fs.exists(path)) { if (!fs.mkdirs(path)) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS004", "mkdir, path [{0}] could not create directory", path); } } } catch (Exception ex) { throw convertException(ex); } } /** * Delete path * * @param context * @param path * @throws ActionExecutorException */ public void delete(Context context, Path path) throws ActionExecutorException { try { validatePath(path, true); FileSystem fs = getFileSystemFor(path, context); if (fs.exists(path)) { if (!fs.delete(path, true)) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS005", "delete, path [{0}] could not delete path", path); } } } catch (Exception ex) { throw convertException(ex); } } /** * Delete path * * @param user * @param group * @param path * @throws ActionExecutorException */ public void delete(String user, String group, Path path) throws ActionExecutorException { try { validatePath(path, true); FileSystem fs = getFileSystemFor(path, user, group); if (fs.exists(path)) { if (!fs.delete(path, true)) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS005", "delete, path [{0}] could not delete path", path); } } } catch (Exception ex) { throw convertException(ex); } } /** * Move source to target * * @param context * @param source * @param target * @param recovery * @throws ActionExecutorException */ public void move(Context context, Path source, Path target, boolean recovery) throws ActionExecutorException { try { validatePath(source, true); validatePath(target, false); FileSystem fs = getFileSystemFor(source, context); if (!fs.exists(source) && !recovery) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS006", "move, source path [{0}] does not exist", source); } Path path = new Path(source, target); if (fs.exists(path) && !recovery) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS007", "move, target path [{0}] already exists", target); } if (!fs.rename(source, target) && !recovery) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS008", "move, could not move [{0}] to [{1}]", source, target); } } catch (Exception ex) { throw convertException(ex); } } void chmod(Context context, Path path, String permissions, boolean dirFiles) throws ActionExecutorException { try { validatePath(path, true); FileSystem fs = getFileSystemFor(path, context); if (!fs.exists(path)) { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS009", "chmod, path [{0}] does not exist", path); } FileStatus pathStatus = fs.getFileStatus(path); Path[] paths; if (dirFiles && pathStatus.isDir()) { FileStatus[] filesStatus = fs.listStatus(path); paths = new Path[filesStatus.length]; for (int i = 0; i < filesStatus.length; i++) { paths[i] = filesStatus[i].getPath(); } } else { paths = new Path[]{path}; } FsPermission newFsPermission = createShortPermission(permissions, path); fs.setPermission(path, newFsPermission); for (Path p : paths) { fs.setPermission(p, newFsPermission); } } catch (Exception ex) { throw convertException(ex); } } FsPermission createShortPermission(String permissions, Path path) throws ActionExecutorException { if (permissions.length() == 3) { char user = permissions.charAt(0); char group = permissions.charAt(1); char other = permissions.charAt(2); int useri = user - '0'; int groupi = group - '0'; int otheri = other - '0'; int mask = useri * 100 + groupi * 10 + otheri; short omask = Short.parseShort(Integer.toString(mask), 8); return new FsPermission(omask); } else { if (permissions.length() == 10) { return FsPermission.valueOf(permissions); } else { throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS010", "chmod, path [{0}] invalid permissions mask [{1}]", path, permissions); } } } @Override public void check(Context context, WorkflowAction action) throws ActionExecutorException { } @Override public void kill(Context context, WorkflowAction action) throws ActionExecutorException { } @Override public void start(Context context, WorkflowAction action) throws ActionExecutorException { try { context.setStartData("-", "-", "-"); Element actionXml = XmlUtils.parseXml(action.getConf()); doOperations(context, actionXml); context.setExecutionData("OK", null); } catch (Exception ex) { throw convertException(ex); } } @Override public void end(Context context, WorkflowAction action) throws ActionExecutorException { String externalStatus = action.getExternalStatus(); WorkflowAction.Status status = externalStatus.equals("OK") ? WorkflowAction.Status.OK : WorkflowAction.Status.ERROR; context.setEndData(status, getActionSignal(status)); if (!context.getProtoActionConf().getBoolean("oozie.action.keep.action.dir", false)) { try { FileSystem fs = context.getAppFileSystem(); fs.delete(context.getActionDir(), true); } catch (Exception ex) { throw convertException(ex); } } } @Override public boolean isCompleted(String externalStatus) { return true; } /** * @param context * @return * @throws HadoopAccessorException * @throws IOException * @throws URISyntaxException */ public Path getRecoveryPath(Context context) throws HadoopAccessorException, IOException, URISyntaxException { return new Path(context.getActionDir(), "fs-" + context.getRecoveryId()); } }