Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
LinuxSuRen
jenkins
提交
ef63ccac
J
jenkins
项目概览
LinuxSuRen
/
jenkins
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
J
jenkins
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
ef63ccac
编写于
2月 04, 2014
作者:
C
CloudBees DEV@Cloud
浏览文件
操作
浏览文件
下载
差异文件
Merge commit '
cd526e99
'
上级
48cb1304
cd526e99
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
218 addition
and
93 deletion
+218
-93
changelog.html
changelog.html
+3
-0
core/src/main/java/hudson/model/Node.java
core/src/main/java/hudson/model/Node.java
+2
-2
core/src/main/java/hudson/model/Slave.java
core/src/main/java/hudson/model/Slave.java
+2
-1
core/src/main/java/hudson/model/WorkspaceCleanupThread.java
core/src/main/java/hudson/model/WorkspaceCleanupThread.java
+56
-90
test/src/test/java/hudson/model/WorkspaceCleanupThreadTest.java
...rc/test/java/hudson/model/WorkspaceCleanupThreadTest.java
+155
-0
未找到文件。
changelog.html
浏览文件 @
ef63ccac
...
...
@@ -67,6 +67,9 @@ Upcoming changes</a>
<li
class=
bug
>
f:combobox is narrow.
(
<a
href=
"https://issues.jenkins-ci.org/browse/JENKINS-21612"
>
issue 21612
</a>
)
<li
class=
bug
>
The workspace cleanup thread failed to handle the modern workspace location on master, and mishandled folders.
(
<a
href=
"https://issues.jenkins-ci.org/browse/JENKINS-21023"
>
issue 21023
</a>
)
<li
class=
bug
>
Fixed missing help items on "Configure Global Security" page
(
<a
href=
"https://issues.jenkins-ci.org/browse/JENKINS-19832"
>
issue 19832
</a>
)
...
...
core/src/main/java/hudson/model/Node.java
浏览文件 @
ef63ccac
...
...
@@ -371,7 +371,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
* null if this node is not connected hence the path is not available
*/
// TODO: should this be modified now that getWorkspace is moved from AbstractProject to AbstractBuild?
public
abstract
FilePath
getWorkspaceFor
(
TopLevelItem
item
);
public
abstract
@CheckForNull
FilePath
getWorkspaceFor
(
TopLevelItem
item
);
/**
* Gets the root directory of this node.
...
...
@@ -384,7 +384,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
* null if the node is offline and hence the {@link FilePath}
* object is not available.
*/
public
abstract
FilePath
getRootPath
();
public
abstract
@CheckForNull
FilePath
getRootPath
();
/**
* Gets the {@link FilePath} on this node.
...
...
core/src/main/java/hudson/model/Slave.java
浏览文件 @
ef63ccac
...
...
@@ -57,6 +57,7 @@ import java.util.Set;
import
javax.servlet.ServletException
;
import
hudson.util.TimeUnit2
;
import
javax.annotation.CheckForNull
;
import
jenkins.model.Jenkins
;
import
jenkins.slaves.WorkspaceLocator
;
...
...
@@ -284,7 +285,7 @@ public abstract class Slave extends Node implements Serializable {
* @return
* null if not connected.
*/
public
FilePath
getWorkspaceRoot
()
{
public
@CheckForNull
FilePath
getWorkspaceRoot
()
{
FilePath
r
=
getRootPath
();
if
(
r
==
null
)
return
null
;
return
r
.
child
(
WORKSPACE_ROOT
);
...
...
core/src/main/java/hudson/model/WorkspaceCleanupThread.java
浏览文件 @
ef63ccac
...
...
@@ -23,18 +23,18 @@
*/
package
hudson.model
;
import
hudson.Extension
;
import
hudson.FilePath
;
import
hudson.Util
;
import
hudson.Extension
;
import
jenkins.model.Jenkins
;
import
java.io.File
;
import
java.io.FileFilter
;
import
java.io.IOException
;
import
java.
io.Serializable
;
import
java.
util.ArrayList
;
import
java.util.Date
;
import
java.util.List
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
javax.annotation.Nonnull
;
import
jenkins.model.Jenkins
;
import
jenkins.model.ModifiableTopLevelItemGroup
;
/**
* Clean up old left-over workspaces from slaves.
...
...
@@ -47,7 +47,7 @@ public class WorkspaceCleanupThread extends AsyncPeriodicWork {
super
(
"Workspace clean-up"
);
}
public
long
getRecurrencePeriod
()
{
@Override
public
long
getRecurrencePeriod
()
{
return
DAY
;
}
...
...
@@ -55,121 +55,87 @@ public class WorkspaceCleanupThread extends AsyncPeriodicWork {
Jenkins
.
getInstance
().
getExtensionList
(
AsyncPeriodicWork
.
class
).
get
(
WorkspaceCleanupThread
.
class
).
run
();
}
// so that this can be easily accessed from sub-routine.
private
TaskListener
listener
;
protected
void
execute
(
TaskListener
listener
)
throws
InterruptedException
,
IOException
{
try
{
if
(
disabled
)
{
LOGGER
.
fine
(
"Disabled. Skipping execution"
);
return
;
}
this
.
listener
=
listener
;
Jenkins
h
=
Jenkins
.
getInstance
();
for
(
Node
n
:
h
.
getNodes
())
if
(
n
instanceof
Slave
)
process
((
Slave
)
n
);
process
(
h
);
}
finally
{
this
.
listener
=
null
;
@Override
protected
void
execute
(
TaskListener
listener
)
throws
InterruptedException
,
IOException
{
if
(
disabled
)
{
LOGGER
.
fine
(
"Disabled. Skipping execution"
);
return
;
}
}
private
void
process
(
Jenkins
h
)
throws
IOException
,
InterruptedException
{
File
jobs
=
new
File
(
h
.
getRootDir
(),
"jobs"
);
File
[]
dirs
=
jobs
.
listFiles
(
DIR_FILTER
);
if
(
dirs
==
null
)
return
;
for
(
File
dir
:
dirs
)
{
FilePath
ws
=
new
FilePath
(
new
File
(
dir
,
"workspace"
));
if
(
shouldBeDeleted
(
dir
.
getName
(),
ws
,
h
))
{
delete
(
ws
);
List
<
Node
>
nodes
=
new
ArrayList
<
Node
>();
Jenkins
j
=
Jenkins
.
getInstance
();
nodes
.
add
(
j
);
nodes
.
addAll
(
j
.
getNodes
());
for
(
TopLevelItem
item
:
j
.
getAllItems
(
TopLevelItem
.
class
))
{
if
(
item
instanceof
ModifiableTopLevelItemGroup
)
{
// no such thing as TopLevelItemGroup, and ItemGroup offers no access to its type parameter
continue
;
// children will typically have their own workspaces as subdirectories; probably no real workspace of its own
}
listener
.
getLogger
().
println
(
"Checking "
+
item
.
getFullDisplayName
());
for
(
Node
node
:
nodes
)
{
FilePath
ws
=
node
.
getWorkspaceFor
(
item
);
if
(
ws
==
null
)
{
continue
;
// offline, fine
}
boolean
check
;
try
{
check
=
shouldBeDeleted
(
item
,
ws
,
node
);
}
catch
(
IOException
x
)
{
x
.
printStackTrace
(
listener
.
error
(
"Failed to check "
+
node
.
getDisplayName
()));
continue
;
}
catch
(
InterruptedException
x
)
{
x
.
printStackTrace
(
listener
.
error
(
"Failed to check "
+
node
.
getDisplayName
()));
continue
;
}
if
(
check
)
{
listener
.
getLogger
().
println
(
"Deleting "
+
ws
+
" on "
+
node
.
getDisplayName
());
try
{
ws
.
deleteRecursive
();
}
catch
(
IOException
x
)
{
x
.
printStackTrace
(
listener
.
error
(
"Failed to delete "
+
ws
+
" on "
+
node
.
getDisplayName
()));
}
catch
(
InterruptedException
x
)
{
x
.
printStackTrace
(
listener
.
error
(
"Failed to delete "
+
ws
+
" on "
+
node
.
getDisplayName
()));
}
}
}
}
}
private
boolean
shouldBeDeleted
(
String
workspaceDirectoryName
,
FilePath
dir
,
Node
n
)
throws
IOException
,
InterruptedException
{
private
boolean
shouldBeDeleted
(
@Nonnull
TopLevelItem
item
,
FilePath
dir
,
Node
n
)
throws
IOException
,
InterruptedException
{
// TODO: the use of remoting is not optimal.
// One remoting can execute "exists", "lastModified", and "delete" all at once.
TopLevelItem
item
=
Jenkins
.
getInstance
().
getItem
(
workspaceDirectoryName
);
if
(!
dir
.
exists
())
// (Could even invert master loop so that one FileCallable takes care of all known items.)
if
(!
dir
.
exists
())
{
LOGGER
.
log
(
Level
.
FINE
,
"Directory {0} does not exist"
,
dir
);
return
false
;
}
// if younger than a month, keep it
long
now
=
new
Date
().
getTime
();
if
(
dir
.
lastModified
()
+
30
*
DAY
>
now
)
{
LOGGER
.
fine
(
"Directory "
+
dir
+
" is only "
+
Util
.
getTimeSpanString
(
now
-
dir
.
lastModified
())+
" old, so not deleting"
);
LOGGER
.
log
(
Level
.
FINE
,
"Directory {0} is only {1} old, so not deleting"
,
new
Object
[]
{
dir
,
Util
.
getTimeSpanString
(
now
-
dir
.
lastModified
())}
);
return
false
;
}
// Could mean that directory doesn't belong to a job. But can also mean that it's a custom workspace belonging to a job.
// So better leave it alone - will still be deleted after 30 days - until we have a proper check for custom workspaces.
// TODO: implement proper check for custom workspaces.
// TODO: If we do the above, could also be good to add checkbox that lets users configure a workspace to never be auto-cleaned.
if
(
item
==
null
)
{
return
false
;
}
// TODO could also be good to add checkbox that lets users configure a workspace to never be auto-cleaned.
if
(
item
instanceof
AbstractProject
<?,?>)
{
AbstractProject
<?,?>
p
=
(
AbstractProject
<?,?>)
item
;
Node
lb
=
p
.
getLastBuiltOn
();
LOGGER
.
finer
(
"Directory "
+
dir
+
" is last built on "
+
lb
);
LOGGER
.
log
(
Level
.
FINER
,
"Directory {0} is last built on {1}"
,
new
Object
[]
{
dir
,
lb
}
);
if
(
lb
!=
null
&&
lb
.
equals
(
n
))
{
// this is the active workspace. keep it.
LOGGER
.
fine
(
"Directory "
+
dir
+
" is the last workspace for "
+
p
);
LOGGER
.
log
(
Level
.
FINE
,
"Directory {0} is the last workspace for {1}"
,
new
Object
[]
{
dir
,
p
}
);
return
false
;
}
if
(!
p
.
getScm
().
processWorkspaceBeforeDeletion
(
p
,
dir
,
n
))
{
LOGGER
.
fine
(
"Directory deletion of "
+
dir
+
" is vetoed by SCM"
);
LOGGER
.
log
(
Level
.
FINE
,
"Directory deletion of {0} is vetoed by SCM"
,
dir
);
return
false
;
}
}
LOGGER
.
finer
(
"Going to delete directory "
+
dir
);
LOGGER
.
log
(
Level
.
FINER
,
"Going to delete directory {0}"
,
dir
);
return
true
;
}
private
void
process
(
Slave
s
)
throws
InterruptedException
{
listener
.
getLogger
().
println
(
"Scanning "
+
s
.
getNodeName
());
try
{
FilePath
path
=
s
.
getWorkspaceRoot
();
if
(
path
==
null
)
return
;
List
<
FilePath
>
dirs
=
path
.
list
(
DIR_FILTER
);
if
(
dirs
==
null
)
return
;
for
(
FilePath
dir
:
dirs
)
{
if
(
shouldBeDeleted
(
dir
.
getName
(),
dir
,
s
))
delete
(
dir
);
}
}
catch
(
IOException
e
)
{
e
.
printStackTrace
(
listener
.
error
(
"Failed on "
+
s
.
getNodeName
()));
}
}
private
void
delete
(
FilePath
dir
)
throws
InterruptedException
{
try
{
listener
.
getLogger
().
println
(
"Deleting "
+
dir
);
dir
.
deleteRecursive
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
(
listener
.
error
(
"Failed to delete "
+
dir
));
}
}
private
static
class
DirectoryFilter
implements
FileFilter
,
Serializable
{
public
boolean
accept
(
File
f
)
{
return
f
.
isDirectory
();
}
private
static
final
long
serialVersionUID
=
1L
;
}
private
static
final
FileFilter
DIR_FILTER
=
new
DirectoryFilter
();
private
static
final
long
DAY
=
1000
*
60
*
60
*
24
;
private
static
final
Logger
LOGGER
=
Logger
.
getLogger
(
WorkspaceCleanupThread
.
class
.
getName
());
/**
...
...
test/src/test/java/hudson/model/WorkspaceCleanupThreadTest.java
0 → 100644
浏览文件 @
ef63ccac
/*
* The MIT License
*
* Copyright 2014 Jesse Glick.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package
hudson.model
;
import
hudson.FilePath
;
import
hudson.FilePath.FileCallable
;
import
hudson.remoting.VirtualChannel
;
import
hudson.slaves.DumbSlave
;
import
hudson.util.StreamTaskListener
;
import
java.io.File
;
import
java.io.IOException
;
import
java.util.logging.ConsoleHandler
;
import
java.util.logging.Handler
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
static
org
.
junit
.
Assert
.*;
import
org.junit.Assume
;
import
org.junit.BeforeClass
;
import
org.junit.Rule
;
import
org.junit.Test
;
import
org.jvnet.hudson.test.Bug
;
import
org.jvnet.hudson.test.JenkinsRule
;
import
org.jvnet.hudson.test.MockFolder
;
public
class
WorkspaceCleanupThreadTest
{
// TODO test that new workspaces are skipped
// TODO test that SCM.processWorkspaceBeforeDeletion can reject
@Rule
public
JenkinsRule
r
=
new
JenkinsRule
();
private
static
final
Logger
logger
=
Logger
.
getLogger
(
WorkspaceCleanupThread
.
class
.
getName
());
@BeforeClass
public
static
void
logging
()
{
logger
.
setLevel
(
Level
.
ALL
);
Handler
handler
=
new
ConsoleHandler
();
handler
.
setLevel
(
Level
.
ALL
);
logger
.
addHandler
(
handler
);
}
@Test
public
void
cleanUpSlaves
()
throws
Exception
{
DumbSlave
s1
=
r
.
createOnlineSlave
();
FreeStyleProject
p
=
r
.
createFreeStyleProject
();
p
.
setAssignedNode
(
s1
);
FreeStyleBuild
b1
=
r
.
assertBuildStatusSuccess
(
p
.
scheduleBuild2
(
0
));
assertEquals
(
s1
,
b1
.
getBuiltOn
());
FilePath
ws1
=
b1
.
getWorkspace
();
assertNotNull
(
ws1
);
ws1
.
act
(
new
Detouch
());
DumbSlave
s2
=
r
.
createOnlineSlave
();
p
.
setAssignedNode
(
s2
);
FreeStyleBuild
b2
=
r
.
assertBuildStatusSuccess
(
p
.
scheduleBuild2
(
0
));
assertEquals
(
s2
,
b2
.
getBuiltOn
());
FilePath
ws2
=
b2
.
getWorkspace
();
assertNotNull
(
ws2
);
ws2
.
act
(
new
Detouch
());
p
.
setAssignedNode
(
r
.
jenkins
);
FreeStyleBuild
b3
=
r
.
assertBuildStatusSuccess
(
p
.
scheduleBuild2
(
0
));
assertEquals
(
r
.
jenkins
,
b3
.
getBuiltOn
());
assertEquals
(
r
.
jenkins
,
p
.
getLastBuiltOn
());
new
WorkspaceCleanupThread
().
execute
(
StreamTaskListener
.
fromStdout
());
assertFalse
(
ws1
.
exists
());
assertFalse
(
ws2
.
exists
());
}
@Bug
(
21023
)
@Test
public
void
modernMasterWorkspaceLocation
()
throws
Exception
{
FreeStyleProject
p
=
r
.
createFreeStyleProject
();
FreeStyleBuild
b1
=
r
.
assertBuildStatusSuccess
(
p
.
scheduleBuild2
(
0
));
assertEquals
(
r
.
jenkins
,
b1
.
getBuiltOn
());
FilePath
ws1
=
b1
.
getWorkspace
();
assertNotNull
(
ws1
);
ws1
.
act
(
new
Detouch
());
DumbSlave
s
=
r
.
createOnlineSlave
();
p
.
setAssignedNode
(
s
);
FreeStyleBuild
b2
=
r
.
assertBuildStatusSuccess
(
p
.
scheduleBuild2
(
0
));
assertEquals
(
s
,
b2
.
getBuiltOn
());
FilePath
ws2
=
b2
.
getWorkspace
();
assertNotNull
(
ws2
);
ws2
.
act
(
new
Detouch
());
assertEquals
(
s
,
p
.
getLastBuiltOn
());
new
WorkspaceCleanupThread
().
execute
(
StreamTaskListener
.
fromStdout
());
assertFalse
(
ws1
.
exists
());
assertTrue
(
ws2
.
exists
());
}
@Bug
(
21023
)
@Test
public
void
jobInFolder
()
throws
Exception
{
MockFolder
d
=
r
.
createFolder
(
"d"
);
FreeStyleProject
p1
=
d
.
createProject
(
FreeStyleProject
.
class
,
"p"
);
FreeStyleBuild
b1
=
r
.
assertBuildStatusSuccess
(
p1
.
scheduleBuild2
(
0
));
assertEquals
(
r
.
jenkins
,
b1
.
getBuiltOn
());
FilePath
ws1
=
b1
.
getWorkspace
();
assertNotNull
(
ws1
);
ws1
.
act
(
new
Detouch
());
DumbSlave
s1
=
r
.
createOnlineSlave
();
p1
.
setAssignedNode
(
s1
);
FreeStyleBuild
b2
=
r
.
assertBuildStatusSuccess
(
p1
.
scheduleBuild2
(
0
));
assertEquals
(
s1
,
b2
.
getBuiltOn
());
FilePath
ws2
=
b2
.
getWorkspace
();
assertNotNull
(
ws2
);
ws2
.
act
(
new
Detouch
());
DumbSlave
s2
=
r
.
createOnlineSlave
();
p1
.
setAssignedNode
(
s2
);
FreeStyleBuild
b3
=
r
.
assertBuildStatusSuccess
(
p1
.
scheduleBuild2
(
0
));
assertEquals
(
s2
,
b3
.
getBuiltOn
());
FilePath
ws3
=
b3
.
getWorkspace
();
assertNotNull
(
ws3
);
ws3
.
act
(
new
Detouch
());
assertEquals
(
s2
,
p1
.
getLastBuiltOn
());
FreeStyleProject
p2
=
d
.
createProject
(
FreeStyleProject
.
class
,
"p2"
);
p2
.
setAssignedNode
(
s1
);
FreeStyleBuild
b4
=
r
.
assertBuildStatusSuccess
(
p2
.
scheduleBuild2
(
0
));
assertEquals
(
s1
,
b4
.
getBuiltOn
());
FilePath
ws4
=
b4
.
getWorkspace
();
assertNotNull
(
ws4
);
ws4
.
act
(
new
Detouch
());
assertEquals
(
s1
,
p2
.
getLastBuiltOn
());
ws2
.
getParent
().
act
(
new
Detouch
());
// ${s1.rootPath}/workspace/d/
new
WorkspaceCleanupThread
().
execute
(
StreamTaskListener
.
fromStdout
());
assertFalse
(
ws1
.
exists
());
assertFalse
(
ws2
.
exists
());
assertTrue
(
ws3
.
exists
());
assertTrue
(
ws4
.
exists
());
}
private
static
final
class
Detouch
implements
FileCallable
<
Void
>
{
@Override
public
Void
invoke
(
File
f
,
VirtualChannel
channel
)
throws
IOException
,
InterruptedException
{
Assume
.
assumeTrue
(
"failed to reset lastModified on "
+
f
,
f
.
setLastModified
(
0
));
return
null
;
}
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录