以前梁大师搞过一个Hudson的构建节点(slave)workspace清理(详见http://www.51testing.com/?uid-13997-action-viewspace-itemid-809114)。现在Hudson和Jenkins基本上分道扬镳了,最新Hudson版本风格与老版本有很大差异,要想升级用到最新的一些特性非常痛苦。而Jenkins则是由Kohsuke Kawaguchi(原Hudson作者)继续发展,其对版本风格更适合升级,所以需要将一些以前弄好的需求代码迁移到Jenkins上。

       Jenkins在删除任务后,本身不会对slave上的workspace进行清理,导致slave磁盘空间占满。目前没有找到任何插件能完成这一工作(插件上也不好搞),于是需要直接修改Jenkins代码。从GitHub上拿到最新的Jenkins代码后,先处理

hudson.model.AbstractProject

protected void performDelete() throws IOException, InterruptedException {
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
        // note start
        /*
        FilePath ws = getWorkspace();
        if(ws!=null) {
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if(on!=null)
                on.getFileSystemProvisioner().discardWorkspace(this,ws);
        }
        */
        // note end
        // modify start
        Map<Node, FilePath> nfMap = getAllBuiltOn();
        for (Map.Entry<Node, FilePath> entry : nfMap.entrySet()) {
            Node n = entry.getKey();
            FilePath fp = entry.getValue();
            getScm().processWorkspaceBeforeDeletion(this, fp, n);
            if (n != null) n.getFileSystemProvisioner().discardWorkspace(this, fp);
        }
        // modify end
        super.performDelete();
    }

先注释掉原有代码,基本没用,只是为了ZFSProvisioner扩展使用,Default的discardWorksapce没有做任何操作。增加一个getAllBuiltOn()方法获取到AbstractProject的所有构建节点,AbstractProject本身获取不到构建节点。但AbstractProject是继承于Job的,所以可以在Job中获取节点。

hudson.model.Job

public Map<Node, FilePath> getAllBuiltOn() {
        List allBuild = getAllBuild();
        Map<Node, FilePath> map = new HashMap<Node, FilePath>();
        Set<Node> nodes = new HashSet<Node>();
        for (Object o : allBuild) {
            AbstractBuild b = (AbstractBuild) o;
            Node n = b.getBuiltOn();
            if (!nodes.contains(n)) {
                FilePath ws = b.getWorkspace();
                if (ws != null) {
                    map.put(n, ws);
                    nodes.add(n);
                }
            }
        }
        return map;
    }
    @Exported
    @QuickSilver
    public List getAllBuild() {
        SortedMap runs = _getRuns();
        List buildList = new ArrayList();
        if (runs.isEmpty()) return buildList;
        for (Map.Entry entry : runs.entrySet()) {
            buildList.add(runs.get(entry.getKey()));
        }
        return buildList;
    }

_getRuns()可以拿到本次Job的运行历史,返回的是Run的子类,AbstractBuild可以拿到slave节点Node和workspace,接下来封装返回即可。接下来是hudson.FileSystemProvisioner.Default内部类对文件的删除,原有代码是没有做任何处理的,所以需要加代码,可以参考ZFSProvisioner的实现。

hudson.FileSystemProvisioner.Default

public void discardWorkspace(AbstractProject<?, ?> project, FilePath ws) throws IOException,InterruptedException {
            ws.act(new FileCallable<Void>() {

                public Void invoke(File f, VirtualChannel channel) throws IOException {
                    if (f != null) {
                        try {
                            Util.deleteRecursive(f);
                        } catch (Throwable e) {
                            // delete failed, eat it.
                        }
                    }
                    return null;
                }
            });
        }

务必将Default实现序列化接口,如果没有删除一个任务会抛出一个异常,异常信息很明显,需要Default类序列化。这里加一个删除任务吃掉任何异常的代码,避免删除文件时出现异常导致任务删除不成功,后继删除操作不能完成(我们有出现需要删除的workspace正好被打开在看问题,结果删除不成功抛了异常,Amon又是一个事件触发的删除,该事件不会第二次来删除,导致Job就残留在Hudson上了)。