001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.action.hadoop;
020
021import java.io.IOException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.security.PrivilegedExceptionAction;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.fs.FSDataOutputStream;
032import org.apache.hadoop.fs.FileStatus;
033import org.apache.hadoop.fs.FileSystem;
034import org.apache.hadoop.fs.FileUtil;
035import org.apache.hadoop.fs.Path;
036import org.apache.hadoop.fs.Trash;
037import org.apache.hadoop.fs.permission.FsPermission;
038import org.apache.hadoop.security.AccessControlException;
039import org.apache.hadoop.security.UserGroupInformation;
040import org.apache.oozie.action.ActionExecutor;
041import org.apache.oozie.action.ActionExecutorException;
042import org.apache.oozie.client.OozieClient;
043import org.apache.oozie.client.WorkflowAction;
044import org.apache.oozie.command.wf.WorkflowXCommand;
045import org.apache.oozie.dependency.FSURIHandler;
046import org.apache.oozie.dependency.URIHandler;
047import org.apache.oozie.service.ConfigurationService;
048import org.apache.oozie.service.HadoopAccessorException;
049import org.apache.oozie.service.HadoopAccessorService;
050import org.apache.oozie.service.Services;
051import org.apache.oozie.service.UserGroupInformationService;
052import org.apache.oozie.service.URIHandlerService;
053import org.apache.oozie.util.XConfiguration;
054import org.apache.oozie.util.XLog;
055import org.apache.oozie.util.XmlUtils;
056import org.jdom.Element;
057
058/**
059 * File system action executor. <p> This executes the file system mkdir, move and delete commands
060 */
061public class FsActionExecutor extends ActionExecutor {
062
063    public static final String ACTION_TYPE = "fs";
064
065    private final int maxGlobCount;
066
067    private final XLog LOG = XLog.getLog(getClass());
068
069    public FsActionExecutor() {
070        super(ACTION_TYPE);
071        maxGlobCount = ConfigurationService.getInt(LauncherAMUtils.CONF_OOZIE_ACTION_FS_GLOB_MAX);
072    }
073
074    /**
075    * Initialize Action.
076    */
077    @Override
078    public void initActionType() {
079        super.initActionType();
080        registerError(AccessControlException.class.getName(), ActionExecutorException.ErrorType.ERROR, "FS014");
081    }
082
083    Path getPath(Element element, String attribute) {
084        String str = element.getAttributeValue(attribute).trim();
085        return new Path(str);
086    }
087
088    void validatePath(Path path, boolean withScheme) throws ActionExecutorException {
089        try {
090            String scheme = path.toUri().getScheme();
091            if (withScheme) {
092                if (scheme == null) {
093                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS001",
094                                                      "Missing scheme in path [{0}]", path);
095                }
096                else {
097                    Services.get().get(HadoopAccessorService.class).checkSupportedFilesystem(path.toUri());
098                }
099            }
100            else {
101                if (scheme != null) {
102                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS002",
103                                                      "Scheme [{0}] not allowed in path [{1}]", scheme, path);
104                }
105            }
106        }
107        catch (HadoopAccessorException hex) {
108            throw convertException(hex);
109        }
110    }
111
112    Path resolveToFullPath(Path nameNode, Path path, boolean withScheme) throws ActionExecutorException {
113        Path fullPath;
114
115        // If no nameNode is given, validate the path as-is and return it as-is
116        if (nameNode == null) {
117            validatePath(path, withScheme);
118            fullPath = path;
119        } else {
120            // If the path doesn't have a scheme or authority, use the nameNode which should have already been verified earlier
121            String pathScheme = path.toUri().getScheme();
122            String pathAuthority = path.toUri().getAuthority();
123            if (pathScheme == null || pathAuthority == null) {
124                if (path.isAbsolute()) {
125                    String nameNodeSchemeAuthority = nameNode.toUri().getScheme() + "://" + nameNode.toUri().getAuthority();
126                    fullPath = new Path(nameNodeSchemeAuthority + path.toString());
127                } else {
128                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS011",
129                            "Path [{0}] cannot be relative", path);
130                }
131            } else {
132                // If the path has a scheme and authority, but its not the nameNode then validate the path as-is and return it as-is
133                // If it is the nameNode, then it should have already been verified earlier so return it as-is
134                if (!nameNode.toUri().getScheme().equals(pathScheme) || !nameNode.toUri().getAuthority().equals(pathAuthority)) {
135                    validatePath(path, withScheme);
136                }
137                fullPath = path;
138            }
139        }
140        return fullPath;
141    }
142
143    void validateSameNN(Path source, Path dest) throws ActionExecutorException {
144        Path destPath = new Path(source, dest);
145        String t = destPath.toUri().getScheme() + destPath.toUri().getAuthority();
146        String s = source.toUri().getScheme() + source.toUri().getAuthority();
147
148        //checking whether NN prefix of source and target is same. can modify this to adjust for a set of multiple whitelisted NN
149        if(!t.equals(s)) {
150            throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS007",
151                    "move, target NN URI different from that of source", dest);
152        }
153    }
154
155    @SuppressWarnings("unchecked")
156    void doOperations(Context context, Element element) throws ActionExecutorException {
157        try {
158            FileSystem fs = context.getAppFileSystem();
159            boolean recovery = fs.exists(getRecoveryPath(context));
160            if (!recovery) {
161                fs.mkdirs(getRecoveryPath(context));
162            }
163
164            Path nameNodePath = null;
165            Element nameNodeElement = element.getChild("name-node", element.getNamespace());
166            if (nameNodeElement != null) {
167                String nameNode = nameNodeElement.getTextTrim();
168                if (nameNode != null) {
169                    nameNodePath = new Path(nameNode);
170                    // Verify the name node now
171                    validatePath(nameNodePath, true);
172                }
173            }
174
175            XConfiguration fsConf = new XConfiguration();
176            Path appPath = new Path(context.getWorkflow().getAppPath());
177            // app path could be a file
178            if (fs.isFile(appPath)) {
179                appPath = appPath.getParent();
180            }
181            JavaActionExecutor.parseJobXmlAndConfiguration(context, element, appPath, fsConf);
182
183            for (Element commandElement : (List<Element>) element.getChildren()) {
184                String command = commandElement.getName();
185                if (command.equals("mkdir")) {
186                    Path path = getPath(commandElement, "path");
187                    mkdir(context, fsConf, nameNodePath, path);
188                }
189                else {
190                    if (command.equals("delete")) {
191                        Path path = getPath(commandElement, "path");
192                        boolean skipTrash = true;
193                        if (commandElement.getAttributeValue("skip-trash") != null &&
194                                commandElement.getAttributeValue("skip-trash").equals("false")) {
195                            skipTrash = false;
196                        }
197                        delete(context, fsConf, nameNodePath, path, skipTrash);
198                    }
199                    else {
200                        if (command.equals("move")) {
201                            Path source = getPath(commandElement, "source");
202                            Path target = getPath(commandElement, "target");
203                            move(context, fsConf, nameNodePath, source, target, recovery);
204                        }
205                        else {
206                            if (command.equals("chmod")) {
207                                Path path = getPath(commandElement, "path");
208                                boolean recursive = commandElement.getChild("recursive", commandElement.getNamespace()) != null;
209                                String str = commandElement.getAttributeValue("dir-files");
210                                boolean dirFiles = (str == null) || Boolean.parseBoolean(str);
211                                String permissionsMask = commandElement.getAttributeValue("permissions").trim();
212                                chmod(context, fsConf, nameNodePath, path, permissionsMask, dirFiles, recursive);
213                            }
214                            else {
215                                if (command.equals("touchz")) {
216                                    Path path = getPath(commandElement, "path");
217                                    touchz(context, fsConf, nameNodePath, path);
218                                }
219                                else {
220                                    if (command.equals("chgrp")) {
221                                        Path path = getPath(commandElement, "path");
222                                        boolean recursive = commandElement.getChild("recursive",
223                                                commandElement.getNamespace()) != null;
224                                        String group = commandElement.getAttributeValue("group");
225                                        String str = commandElement.getAttributeValue("dir-files");
226                                        boolean dirFiles = (str == null) || Boolean.parseBoolean(str);
227                                        chgrp(context, fsConf, nameNodePath, path, context.getWorkflow().getUser(),
228                                                group, dirFiles, recursive);
229                                    }
230                                    else {
231                                        if (command.equals("setrep")) {
232                                            Path path = getPath(commandElement, "path");
233                                            String replicationFactor =
234                                            commandElement.getAttributeValue("replication-factor");
235                                            if (commandElement.getAttributeValue("replication-factor") != null) {
236                                                setrep(context, path, Short.parseShort(replicationFactor));
237                                            }
238                                        }
239                                    }
240                                }
241                            }
242                        }
243                    }
244                }
245            }
246        }
247        catch (Exception ex) {
248            throw convertException(ex);
249        }
250    }
251
252    void chgrp(Context context, XConfiguration fsConf, Path nameNodePath, Path path, String user, String group,
253            boolean dirFiles, boolean recursive) throws ActionExecutorException {
254
255        LOG.info("Setting ownership for [{0}] to group: [{1}], user: [{2}]. Recursive mode: [{3}]", path, group, user, recursive);
256        HashMap<String, String> argsMap = new HashMap<String, String>();
257        argsMap.put("user", user);
258        argsMap.put("group", group);
259        try {
260            FileSystem fs = getFileSystemFor(path, context, fsConf);
261            path = resolveToFullPath(nameNodePath, path, true);
262            Path[] pathArr = FileUtil.stat2Paths(fs.globStatus(path));
263            if (pathArr == null || pathArr.length == 0) {
264                throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS009", "chgrp"
265                        + ", path(s) that matches [{0}] does not exist", path);
266            }
267            checkGlobMax(pathArr);
268            for (Path p : pathArr) {
269                recursiveFsOperation("chgrp", fs, nameNodePath, p, argsMap, dirFiles, recursive, true);
270            }
271        }
272        catch (Exception ex) {
273            throw convertException(ex);
274        }
275    }
276
277    private void recursiveFsOperation(String op, FileSystem fs, Path nameNodePath, Path path,
278            Map<String, String> argsMap, boolean dirFiles, boolean recursive, boolean isRoot)
279            throws ActionExecutorException {
280
281        try {
282            FileStatus pathStatus = fs.getFileStatus(path);
283            List<Path> paths = new ArrayList<Path>();
284
285            if (dirFiles && pathStatus.isDirectory()) {
286                if (isRoot) {
287                    paths.add(path);
288                }
289                FileStatus[] filesStatus = fs.listStatus(path);
290                for (int i = 0; i < filesStatus.length; i++) {
291                    Path p = filesStatus[i].getPath();
292                    paths.add(p);
293                    if (recursive && filesStatus[i].isDirectory()) {
294                        recursiveFsOperation(op, fs, null, p, argsMap, dirFiles, recursive, false);
295                    }
296                }
297            }
298            else {
299                paths.add(path);
300            }
301            for (Path p : paths) {
302                doFsOperation(op, fs, p, argsMap);
303            }
304        }
305        catch (Exception ex) {
306            throw convertException(ex);
307        }
308    }
309
310    private void doFsOperation(String op, FileSystem fs, Path p, Map<String, String> argsMap)
311            throws ActionExecutorException, IOException {
312        if (op.equals("chmod")) {
313            String permissions = argsMap.get("permissions");
314            FsPermission newFsPermission = createShortPermission(permissions, p);
315            fs.setPermission(p, newFsPermission);
316        }
317        else if (op.equals("chgrp")) {
318            String user = argsMap.get("user");
319            String group = argsMap.get("group");
320            fs.setOwner(p, user, group);
321        }
322    }
323
324    /**
325     * @param path
326     * @param context
327     * @param fsConf
328     * @return FileSystem
329     * @throws HadoopAccessorException
330     */
331    private FileSystem getFileSystemFor(Path path, Context context, XConfiguration fsConf) throws HadoopAccessorException {
332        String user = context.getWorkflow().getUser();
333        HadoopAccessorService has = Services.get().get(HadoopAccessorService.class);
334        Configuration conf = has.createConfiguration(path.toUri().getAuthority());
335        XConfiguration.copy(context.getProtoActionConf(), conf);
336        if (fsConf != null) {
337            XConfiguration.copy(fsConf, conf);
338        }
339        return has.createFileSystem(user, path.toUri(), conf);
340    }
341
342    /**
343     * @param path
344     * @param user
345     * @return FileSystem
346     * @throws HadoopAccessorException
347     */
348    private FileSystem getFileSystemFor(Path path, String user) throws HadoopAccessorException {
349        HadoopAccessorService has = Services.get().get(HadoopAccessorService.class);
350        Configuration jobConf = has.createConfiguration(path.toUri().getAuthority());
351        return has.createFileSystem(user, path.toUri(), jobConf);
352    }
353
354    void mkdir(Context context, Path path) throws ActionExecutorException {
355        mkdir(context, null, null, path);
356    }
357
358    void mkdir(Context context, XConfiguration fsConf, Path nameNodePath, Path path) throws ActionExecutorException {
359        LOG.info("Creating directory [{0}]", path);
360        try {
361            path = resolveToFullPath(nameNodePath, path, true);
362            FileSystem fs = getFileSystemFor(path, context, fsConf);
363
364            if (!fs.exists(path)) {
365                if (!fs.mkdirs(path)) {
366                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS004",
367                                                      "mkdir, path [{0}] could not create directory", path);
368                }
369            } else {
370                LOG.info("[{0}] already exist, no need for creation", path);
371            }
372        }
373        catch (Exception ex) {
374            throw convertException(ex);
375        }
376    }
377
378    /**
379     * Delete path
380     *
381     * @param context
382     * @param path
383     * @throws ActionExecutorException
384     */
385    public void delete(Context context, Path path) throws ActionExecutorException {
386        delete(context, null, null, path, true);
387    }
388
389    /**
390     * Delete path
391     *
392     * @param context
393     * @param fsConf
394     * @param nameNodePath
395     * @param path
396     * @param skipTrash flag to skip the trash.
397     * @throws ActionExecutorException
398     */
399    public void delete(Context context, XConfiguration fsConf, Path nameNodePath, Path path, boolean skipTrash)
400            throws ActionExecutorException {
401        LOG.info("Deleting [{0}]. Skipping trash: [{1}]", path, skipTrash);
402        URI uri = path.toUri();
403        URIHandler handler;
404        org.apache.oozie.dependency.URIHandler.Context hcatContext = null;
405        try {
406            handler = Services.get().get(URIHandlerService.class).getURIHandler(uri);
407            if (handler instanceof FSURIHandler) {
408                // Use legacy code to handle hdfs partition deletion
409                path = resolveToFullPath(nameNodePath, path, true);
410                final FileSystem fs = getFileSystemFor(path, context, fsConf);
411                Path[] pathArr = FileUtil.stat2Paths(fs.globStatus(path));
412                if (pathArr != null && pathArr.length > 0) {
413                    checkGlobMax(pathArr);
414                    for (final Path p : pathArr) {
415                        if (fs.exists(p)) {
416                            if (!skipTrash) {
417                                // Moving directory/file to trash of user.
418                                UserGroupInformationService ugiService = Services.get().get(UserGroupInformationService.class);
419                                UserGroupInformation ugi = ugiService.getProxyUser(fs.getConf().get(OozieClient.USER_NAME));
420                                ugi.doAs(new PrivilegedExceptionAction<FileSystem>() {
421                                    @Override
422                                    public FileSystem run() throws Exception {
423                                        Trash trash = new Trash(fs.getConf());
424                                        if (!trash.moveToTrash(p)) {
425                                            throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS005",
426                                                    "Could not move path [{0}] to trash on delete", p);
427                                        }
428                                        return null;
429                                    }
430                                });
431                            }
432                            else if (!fs.delete(p, true)) {
433                                throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS005",
434                                        "delete, path [{0}] could not delete path", p);
435                            }
436                        }
437                    }
438                }
439            } else {
440                hcatContext = handler.getContext(uri, fsConf, context.getWorkflow().getUser(), false);
441                handler.delete(uri, hcatContext);
442            }
443        }
444        catch (Exception ex) {
445            throw convertException(ex);
446        }
447        finally{
448            if (hcatContext != null) {
449                hcatContext.destroy();
450            }
451        }
452    }
453
454    /**
455     * Delete path
456     *
457     * @param user
458     * @param group
459     * @param path
460     * @throws ActionExecutorException
461     */
462    public void delete(String user, String group, Path path) throws ActionExecutorException {
463        try {
464            validatePath(path, true);
465            FileSystem fs = getFileSystemFor(path, user);
466
467            if (fs.exists(path)) {
468                if (!fs.delete(path, true)) {
469                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS005",
470                            "delete, path [{0}] could not delete path", path);
471                }
472            }
473        }
474        catch (Exception ex) {
475            throw convertException(ex);
476        }
477    }
478
479    /**
480     * Move source to target
481     *
482     * @param context
483     * @param source
484     * @param target
485     * @param recovery
486     * @throws ActionExecutorException
487     */
488    public void move(Context context, Path source, Path target, boolean recovery) throws ActionExecutorException {
489        move(context, null, null, source, target, recovery);
490    }
491
492    /**
493     * Move source to target
494     *
495     * @param context
496     * @param fsConf
497     * @param nameNodePath
498     * @param source
499     * @param target
500     * @param recovery
501     * @throws ActionExecutorException
502     */
503    public void move(Context context, XConfiguration fsConf, Path nameNodePath, Path source, Path target, boolean recovery)
504            throws ActionExecutorException {
505        LOG.info("Moving [{0}] to [{1}]", source, target);
506        try {
507            source = resolveToFullPath(nameNodePath, source, true);
508            validateSameNN(source, target);
509            FileSystem fs = getFileSystemFor(source, context, fsConf);
510            Path[] pathArr = FileUtil.stat2Paths(fs.globStatus(source));
511            if (( pathArr == null || pathArr.length == 0 ) ){
512                if (!recovery) {
513                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS006",
514                        "move, source path [{0}] does not exist", source);
515                } else {
516                    return;
517                }
518            }
519            if (pathArr.length > 1 && (!fs.exists(target) || fs.isFile(target))) {
520                if(!recovery) {
521                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS012",
522                            "move, could not rename multiple sources to the same target name");
523                } else {
524                    return;
525                }
526            }
527            checkGlobMax(pathArr);
528            for (Path p : pathArr) {
529                if (!fs.rename(p, target) && !recovery) {
530                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS008",
531                            "move, could not move [{0}] to [{1}]", p, target);
532                }
533            }
534        }
535        catch (Exception ex) {
536            throw convertException(ex);
537        }
538    }
539
540    void chmod(Context context, Path path, String permissions, boolean dirFiles, boolean recursive) throws ActionExecutorException {
541        chmod(context, null, null, path, permissions, dirFiles, recursive);
542    }
543
544    void chmod(Context context, XConfiguration fsConf, Path nameNodePath, Path path, String permissions,
545            boolean dirFiles, boolean recursive) throws ActionExecutorException {
546
547        LOG.info("Setting permissions [{0}] on [{1}]. Recursive mode: [{2}]", permissions, path, recursive);
548        HashMap<String, String> argsMap = new HashMap<String, String>();
549        argsMap.put("permissions", permissions);
550        try {
551            FileSystem fs = getFileSystemFor(path, context, fsConf);
552            path = resolveToFullPath(nameNodePath, path, true);
553            Path[] pathArr = FileUtil.stat2Paths(fs.globStatus(path));
554            if (pathArr == null || pathArr.length == 0) {
555                throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS009", "chmod"
556                        + ", path(s) that matches [{0}] does not exist", path);
557            }
558            checkGlobMax(pathArr);
559            for (Path p : pathArr) {
560                recursiveFsOperation("chmod", fs, nameNodePath, p, argsMap, dirFiles, recursive, true);
561            }
562
563        }
564        catch (Exception ex) {
565            throw convertException(ex);
566        }
567    }
568
569    void touchz(Context context, Path path) throws ActionExecutorException {
570        touchz(context, null, null, path);
571    }
572
573    void touchz(Context context, XConfiguration fsConf, Path nameNodePath, Path path) throws ActionExecutorException {
574
575        LOG.info ("Performing touch on [{0}]", path);
576        try {
577            path = resolveToFullPath(nameNodePath, path, true);
578            FileSystem fs = getFileSystemFor(path, context, fsConf);
579
580            FileStatus st;
581            if (fs.exists(path)) {
582                st = fs.getFileStatus(path);
583                if (st.isDirectory()) {
584                    throw new Exception(path.toString() + " is a directory");
585                } else if (st.getLen() != 0) {
586                    throw new Exception(path.toString() + " must be a zero-length file");
587                }
588            }
589            FSDataOutputStream out = fs.create(path);
590            out.close();
591        }
592        catch (Exception ex) {
593            throw convertException(ex);
594        }
595    }
596
597    FsPermission createShortPermission(String permissions, Path path) throws ActionExecutorException {
598        if (permissions.length() == 3) {
599            char user = permissions.charAt(0);
600            char group = permissions.charAt(1);
601            char other = permissions.charAt(2);
602            int useri = user - '0';
603            int groupi = group - '0';
604            int otheri = other - '0';
605            int mask = useri * 100 + groupi * 10 + otheri;
606            short omask = Short.parseShort(Integer.toString(mask), 8);
607            return new FsPermission(omask);
608        }
609        else {
610            if (permissions.length() == 10) {
611                return FsPermission.valueOf(permissions);
612            }
613            else {
614                throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS010",
615                                                  "chmod, path [{0}] invalid permissions mask [{1}]", path, permissions);
616            }
617        }
618    }
619
620    @Override
621    public void check(Context context, WorkflowAction action) throws ActionExecutorException {
622    }
623
624    @Override
625    public void kill(Context context, WorkflowAction action) throws ActionExecutorException {
626    }
627
628    @Override
629    public void start(Context context, WorkflowAction action) throws ActionExecutorException {
630        LOG.info("Starting action");
631        try {
632            context.setStartData("-", "-", "-");
633            Element actionXml = XmlUtils.parseXml(action.getConf());
634            doOperations(context, actionXml);
635            context.setExecutionData("OK", null);
636        }
637        catch (Exception ex) {
638            throw convertException(ex);
639        }
640    }
641
642    @Override
643    public void end(Context context, WorkflowAction action) throws ActionExecutorException {
644        String externalStatus = action.getExternalStatus();
645        WorkflowAction.Status status = externalStatus.equals("OK") ? WorkflowAction.Status.OK :
646                                       WorkflowAction.Status.ERROR;
647        context.setEndData(status, getActionSignal(status));
648        if (!context.getProtoActionConf().getBoolean(WorkflowXCommand.KEEP_WF_ACTION_DIR, false)) {
649            try {
650                FileSystem fs = context.getAppFileSystem();
651                fs.delete(context.getActionDir(), true);
652            }
653            catch (Exception ex) {
654                throw convertException(ex);
655            }
656        }
657        LOG.info("Action ended with external status [{0}]", action.getExternalStatus());
658    }
659
660    @Override
661    public boolean isCompleted(String externalStatus) {
662        return true;
663    }
664
665    /**
666     * @param context
667     * @return Path returns recovery path
668     * @throws HadoopAccessorException
669     * @throws IOException
670     * @throws URISyntaxException
671     */
672    public Path getRecoveryPath(Context context) throws HadoopAccessorException, IOException, URISyntaxException {
673        return new Path(context.getActionDir(), "fs-" + context.getRecoveryId());
674    }
675
676    private void checkGlobMax(Path[] pathArr) throws ActionExecutorException {
677        if(pathArr.length > maxGlobCount) {
678            throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FS013",
679                    "too many globbed files/dirs to do FS operation");
680        }
681    }
682
683    void setrep(Context context, Path path, short replicationFactor)
684            throws ActionExecutorException, HadoopAccessorException {
685        LOG.info("Setting replication factor: [{0}] for [{1}]", replicationFactor, path);
686        try {
687            path = resolveToFullPath(null, path, true);
688            FileSystem fs = getFileSystemFor(path, context, null);
689
690            if (fs.isFile(path)) {
691                fs.setReplication(path, replicationFactor);
692            }
693        } catch (IOException ex) {
694            convertException(ex);
695        }
696    }
697
698    public boolean supportsConfigurationJobXML() {
699        return true;
700    }
701}