Skip to content

Commit af593f3

Browse files
authored
Merge pull request #85 from jenkinsci/recurring-maintenance
[JENKINS-69843] recurring maintenances
2 parents 3222750 + b666ea0 commit af593f3

File tree

29 files changed

+1183
-137
lines changed

29 files changed

+1183
-137
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ Via the "Edit" button one can edit existing maintenance windows (and also add an
4545
Going to "Manage Jenkins->Agent Maintenance" will present you a list of all currently defined maintenance windows of all agents.
4646
Using the button "Add" allows to use a label expression to select a list of agents for which to apply the maintenance window.
4747

48+
## Recurring maintenance windows
49+
It is also possible to define recurring maintenance windows. Using a cron syntax you can specify the start time of the downtime and a duration.
50+
Recurring maintenance windows are added as planned maintenance window 7 days before they start by default. This way you can easily cancel or modify them before
51+
they start. The lead time for adding recurring maintenance windows can be changed by setting the system property com.sap.prd.jenkins.plugins.agent_maintenance.RecurringMaintenanceWindow.LEAD_TIME_DAYS
52+
during start up of Jenkins. Note that changing the lead time can have unwanted side effects like duplicated/missing maintenance windows.
53+
54+
Changing a recurring maintenance will not change already scheduled maintenance windows for it. Those need to be adjusted manually.
4855

4956
## Best practices
5057

pom.xml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@
3939
</scm>
4040

4141
<properties>
42-
<revision>1</revision>
42+
<revision>2</revision>
4343
<changelist>999999-SNAPSHOT</changelist>
4444
<gitHubRepo>jenkinsci/agent-maintenance-plugin</gitHubRepo>
4545
<jenkins.version>2.387.2</jenkins.version>
4646
<powermock.version>1.6.1</powermock.version>
4747
<checkstyle.version>10.12.1</checkstyle.version>
48+
<hpi.compatibleSinceVersion>2.0</hpi.compatibleSinceVersion>
4849
</properties>
4950

5051
<dependencyManagement>
@@ -59,10 +60,15 @@
5960
</dependencies>
6061
</dependencyManagement>
6162
<dependencies>
62-
<dependency>
63-
<groupId>io.jenkins.plugins</groupId>
64-
<artifactId>ionicons-api</artifactId>
65-
</dependency>
63+
<dependency>
64+
<groupId>io.jenkins.plugins</groupId>
65+
<artifactId>ionicons-api</artifactId>
66+
</dependency>
67+
<dependency>
68+
<groupId>com.cronutils</groupId>
69+
<artifactId>cron-utils</artifactId>
70+
<version>9.2.0</version>
71+
</dependency>
6672
<dependency>
6773
<groupId>org.mockito</groupId>
6874
<artifactId>mockito-core</artifactId>

src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/AgentMaintenanceRetentionStrategy.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public boolean isManualLaunchAllowed(final SlaveComputer c) {
6262
@GuardedBy("hudson.model.Queue.lock")
6363
public synchronized long check(final SlaveComputer c) {
6464
MaintenanceWindow maintenance = MaintenanceHelper.getInstance().getMaintenance(c.getName());
65+
MaintenanceHelper.getInstance().checkRecurring(c.getName());
6566
LOGGER.log(Level.FINER, "Checking for Maintenance Window for agent {0}. online = {1}, idle = {2}",
6667
new Object[] { c.getName(), c.isOnline(), c.isIdle() });
6768
if (maintenance != null) {

src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceAction.java

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.HashMap;
1717
import java.util.List;
1818
import java.util.Map;
19+
import java.util.Set;
1920
import java.util.SortedSet;
2021
import java.util.logging.Level;
2122
import java.util.logging.Logger;
@@ -88,6 +89,10 @@ public Class<MaintenanceWindow> getMaintenanceWindowClass() {
8889
return MaintenanceWindow.class;
8990
}
9091

92+
public Class<RecurringMaintenanceWindow> getRecurringMaintenanceWindowClass() {
93+
return RecurringMaintenanceWindow.class;
94+
}
95+
9196
public boolean isEnabled() {
9297
return computer.getRetentionStrategy() instanceof AgentMaintenanceRetentionStrategy;
9398
}
@@ -155,7 +160,20 @@ public SortedSet<MaintenanceWindow> getMaintenanceWindows() {
155160
}
156161

157162
/**
158-
* UI method to add a smaintenance window.
163+
* Returns a list of recurring maintenance windows.
164+
*
165+
* @return A list of recurring maintenance windows
166+
*/
167+
public Set<RecurringMaintenanceWindow> getRecurringMaintenanceWindows() {
168+
try {
169+
return Collections.unmodifiableSet(MaintenanceHelper.getInstance().getRecurringMaintenanceWindows(computer.getName()));
170+
} catch (IOException e) {
171+
return Collections.emptySortedSet();
172+
}
173+
}
174+
175+
/**
176+
* UI method to add a maintenance window.
159177
*
160178
* @param req Stapler Request
161179
* @return Response containing the result of the add
@@ -172,6 +190,24 @@ public HttpResponse doAdd(StaplerRequest req) throws IOException, ServletExcepti
172190
return FormApply.success(".");
173191
}
174192

193+
/**
194+
* UI method to add a recurring maintenance window.
195+
*
196+
* @param req Stapler Request
197+
* @param rsp Stapler Response
198+
* @throws IOException when writing fails
199+
* @throws ServletException if an error occurs reading the form
200+
*/
201+
@POST
202+
public void doAddRecurring(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
203+
computer.checkAnyPermission(CONFIGURE_AND_DISCONNECT);
204+
205+
JSONObject src = req.getSubmittedForm();
206+
RecurringMaintenanceWindow rmw = req.bindJSON(RecurringMaintenanceWindow.class, src);
207+
MaintenanceHelper.getInstance().addRecurringMaintenanceWindow(computer.getName(), rmw);
208+
rsp.sendRedirect(".");
209+
}
210+
175211
/**
176212
* UI method to delete multiple maintenance windows.
177213
*
@@ -214,6 +250,26 @@ public Map<String, Boolean> getMaintenanceStatus() {
214250
return statusList;
215251
}
216252

253+
/**
254+
* UI method to delete multiple recurring maintenance windows.
255+
*
256+
* @param ids A list of recurring maintenance window ids to delete
257+
*/
258+
@JavaScriptMethod
259+
public String[] deleteMultipleRecurring(String[] ids) {
260+
computer.checkAnyPermission(CONFIGURE_AND_DISCONNECT);
261+
List<String> deletedList = new ArrayList<>();
262+
for (String id : ids) {
263+
try {
264+
MaintenanceHelper.getInstance().deleteRecurringMaintenanceWindow(computer.getName(), id);
265+
deletedList.add(id);
266+
} catch (Throwable e) {
267+
LOGGER.log(Level.WARNING, "Error while deleting maintenance window", e);
268+
}
269+
}
270+
return deletedList.toArray(new String[0]);
271+
}
272+
217273
/**
218274
* UI method to delete a maintenance window.
219275
*
@@ -236,6 +292,28 @@ public boolean deleteMaintenance(String id) {
236292
return false;
237293
}
238294

295+
/**
296+
* UI method to delete a recurring maintenance window.
297+
*
298+
* @param id The id of the maintenance to delete
299+
*/
300+
@JavaScriptMethod
301+
public boolean deleteRecurringMaintenance(String id) {
302+
if (computer.hasAnyPermission(CONFIGURE_AND_DISCONNECT)) {
303+
if (Util.fixEmptyAndTrim(id) == null) {
304+
return false;
305+
}
306+
try {
307+
MaintenanceHelper.getInstance().deleteRecurringMaintenanceWindow(computer.getName(), id);
308+
return true;
309+
} catch (IOException ioe) {
310+
LOGGER.log(Level.WARNING, "Failed to delete recurring maintenance window.", ioe);
311+
return false;
312+
}
313+
}
314+
return false;
315+
}
316+
239317
/**
240318
* UI method to submit the configuration.
241319
*
@@ -251,12 +329,18 @@ public synchronized HttpResponse doConfigSubmit(StaplerRequest req) throws IOExc
251329
JSONObject src = req.getSubmittedForm();
252330

253331
List<MaintenanceWindow> newTargets = req.bindJSONToList(MaintenanceWindow.class, src.get("maintenanceWindows"));
332+
List<RecurringMaintenanceWindow> newRecurringTargets = req.bindJSONToList(RecurringMaintenanceWindow.class,
333+
src.get("recurringMaintenanceWindows"));
254334

255-
SortedSet<MaintenanceWindow> mwlist = MaintenanceHelper.getInstance().getMaintenanceWindows(computer.getName());
256-
synchronized (mwlist) {
257-
mwlist.clear();
258-
mwlist.addAll(newTargets);
259-
MaintenanceHelper.getInstance().saveMaintenanceWindows(computer.getName(), mwlist);
335+
MaintenanceDefinitions md = MaintenanceHelper.getInstance().getMaintenanceDefinitions(computer.getName());
336+
synchronized (md) {
337+
SortedSet<MaintenanceWindow> scheduled = md.getScheduled();
338+
Set<RecurringMaintenanceWindow> recurring = md.getRecurring();
339+
scheduled.clear();
340+
scheduled.addAll(newTargets);
341+
recurring.clear();
342+
recurring.addAll(newRecurringTargets);
343+
MaintenanceHelper.getInstance().saveMaintenanceWindows(computer.getName(), md);
260344
}
261345
return FormApply.success(".");
262346
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.sap.prd.jenkins.plugins.agent_maintenance;
2+
3+
import java.util.Set;
4+
import java.util.SortedSet;
5+
6+
/**
7+
* Container that holds the scheduled and recurring maintenance windows for an agent.
8+
*/
9+
public class MaintenanceDefinitions {
10+
private SortedSet<MaintenanceWindow> scheduled;
11+
private Set<RecurringMaintenanceWindow> recurring;
12+
13+
/**
14+
* Create definitions container.
15+
*
16+
* @param scheduled A set of scheduled maintenance windows
17+
* @param recurring A set of recurring maintenance windows
18+
*/
19+
public MaintenanceDefinitions(SortedSet<MaintenanceWindow> scheduled, Set<RecurringMaintenanceWindow> recurring) {
20+
this.scheduled = scheduled;
21+
this.recurring = recurring;
22+
}
23+
24+
/**
25+
* Get the scheduled maintenance windows.
26+
*
27+
* @return Set of scheduled maintenance windows
28+
*/
29+
public SortedSet<MaintenanceWindow> getScheduled() {
30+
return scheduled;
31+
}
32+
33+
/**
34+
* Get the recurring maintenance windows.
35+
*
36+
* @return Set of recurring maintenance windows
37+
*/
38+
public Set<RecurringMaintenanceWindow> getRecurring() {
39+
return recurring;
40+
}
41+
}

0 commit comments

Comments
 (0)