Skip to content

Commit 45e38c1

Browse files
authored
Using sc for service start/stop in tests, plus busy waiting (#137451)
This PR attempts to fix (definitely) Windows services tests. We used procrun as a service control tool; however, procrun (as a service) suffers from a race condition and procrun as a tool seems to suffer from it. Furthermore, the implementation of stop in procrun is (at the very least), "strange": it seems to wait for he service to stop, but it's not guaranteed to wait till the end, returning an error (potentially) In general, this is true for all (most) of the service control tools: the return when the service is still in a STOP_PENDING state. We actually have to "busy wait" for the service to be really STOPPED. This PR adds the busy wait and adjusts how we stop the service slightly. It also moves to the standard Windows sc.exe tool for service control operations (start, stop and delete) in tests. Fixes: #113177 Fixes: #113160 Fixes: #113219 Fixes: #113313
1 parent 6f90938 commit 45e38c1

File tree

3 files changed

+62
-42
lines changed

3 files changed

+62
-42
lines changed

muted-tests.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,6 @@ tests:
2929
- class: org.elasticsearch.xpack.sql.qa.security.JdbcSqlSpecIT
3030
method: test {case-functions.testSelectInsertWithLcaseAndLengthWithOrderBy}
3131
issue: https://github.com/elastic/elasticsearch/issues/112642
32-
- class: org.elasticsearch.packaging.test.WindowsServiceTests
33-
method: test30StartStop
34-
issue: https://github.com/elastic/elasticsearch/issues/113160
35-
- class: org.elasticsearch.packaging.test.WindowsServiceTests
36-
method: test33JavaChanged
37-
issue: https://github.com/elastic/elasticsearch/issues/113177
38-
- class: org.elasticsearch.packaging.test.WindowsServiceTests
39-
method: test80JavaOptsInEnvVar
40-
issue: https://github.com/elastic/elasticsearch/issues/113219
41-
- class: org.elasticsearch.packaging.test.WindowsServiceTests
42-
method: test81JavaOptsInJvmOptions
43-
issue: https://github.com/elastic/elasticsearch/issues/113313
4432
- class: org.elasticsearch.xpack.transform.integration.TransformIT
4533
method: testStopWaitForCheckpoint
4634
issue: https://github.com/elastic/elasticsearch/issues/106113

qa/packaging/src/test/java/org/elasticsearch/packaging/test/WindowsServiceTests.java

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.UncheckedIOException;
2121
import java.nio.file.Files;
2222
import java.nio.file.Path;
23+
import java.time.Duration;
2324

2425
import static com.carrotsearch.randomizedtesting.RandomizedTest.assumeTrue;
2526
import static org.elasticsearch.packaging.util.Archives.installArchive;
@@ -44,7 +45,19 @@ public static void ensureWindows() {
4445

4546
@After
4647
public void uninstallService() {
47-
sh.runIgnoreExitCode(serviceScript + " remove");
48+
sh.runIgnoreExitCode(deleteCommand(DEFAULT_ID));
49+
}
50+
51+
private static String startCommand(String serviceId) {
52+
return "\"sc.exe start " + serviceId + "\"";
53+
}
54+
55+
private static String stopCommand(String serviceId) {
56+
return "\"sc.exe stop " + serviceId + "\"";
57+
}
58+
59+
private static String deleteCommand(String serviceId) {
60+
return "\"sc.exe delete " + serviceId + "\"";
4861
}
4962

5063
private void assertService(String id, String status) {
@@ -53,6 +66,29 @@ private void assertService(String id, String status) {
5366
assertThat(result.stdout(), containsString("Status : " + status));
5467
}
5568

69+
private void waitForStop(String id, Duration timeout) {
70+
var stopped = false;
71+
var start = System.currentTimeMillis();
72+
while (stopped == false) {
73+
Result result = sh.run("(Get-Service " + id + " ).\"Status\"");
74+
if (result.exitCode() != 0) {
75+
logger.warn("Cannot get status for {}: stdout:[{}], stderr:[{}]", id, result.stdout(), result.stderr());
76+
break;
77+
}
78+
stopped = "Stopped".equalsIgnoreCase(result.stdout());
79+
Duration elapsed = Duration.ofMillis(System.currentTimeMillis() - start);
80+
if (elapsed.compareTo(timeout) > 0) {
81+
logger.warn("Timeout waiting for stop {}: stdout:[{}], stderr:[{}]", id, result.stdout(), result.stderr());
82+
break;
83+
}
84+
try {
85+
Thread.sleep(2000);
86+
} catch (InterruptedException e) {
87+
break;
88+
}
89+
}
90+
}
91+
5692
// runs the service command, dumping all log files on failure
5793
private Result assertCommand(String script) {
5894
Result result = sh.runIgnoreExitCode(script);
@@ -108,12 +144,12 @@ public void test10InstallArchive() throws Exception {
108144
public void test12InstallService() {
109145
sh.run(serviceScript + " install");
110146
assertService(DEFAULT_ID, "Stopped");
111-
sh.run(serviceScript + " remove");
147+
sh.run(deleteCommand(DEFAULT_ID));
112148
}
113149

114150
public void test15RemoveNotInstalled() {
115-
Result result = assertFailure(serviceScript + " remove", 1);
116-
assertThat(result.stderr(), containsString("Failed removing '" + DEFAULT_ID + "' service"));
151+
Result result = assertFailure(deleteCommand(DEFAULT_ID), 1);
152+
assertThat(result.stdout(), containsString("The specified service does not exist as an installed service"));
117153
}
118154

119155
public void test16InstallSpecialCharactersInJdkPath() throws IOException {
@@ -126,33 +162,37 @@ public void test16InstallSpecialCharactersInJdkPath() throws IOException {
126162
Result result = sh.run(serviceScript + " install");
127163
assertThat(result.stdout(), containsString("The service 'elasticsearch-service-x64' has been installed"));
128164
} finally {
129-
sh.runIgnoreExitCode(serviceScript + " remove");
165+
sh.runIgnoreExitCode(deleteCommand(DEFAULT_ID));
130166
mv(relocatedJdk, installation.bundledJdk);
131167
}
132168
}
133169

134170
public void test20CustomizeServiceId() {
135171
String serviceId = "my-es-service";
136-
sh.getEnv().put("SERVICE_ID", serviceId);
137-
sh.run(serviceScript + " install");
138-
assertService(serviceId, "Stopped");
139-
sh.run(serviceScript + " remove");
172+
try {
173+
sh.getEnv().put("SERVICE_ID", serviceId);
174+
sh.run(serviceScript + " install");
175+
assertService(serviceId, "Stopped");
176+
} finally {
177+
sh.run(deleteCommand(serviceId));
178+
}
140179
}
141180

142181
public void test21CustomizeServiceDisplayName() {
143182
String displayName = "my es service display name";
144183
sh.getEnv().put("SERVICE_DISPLAY_NAME", displayName);
145184
sh.run(serviceScript + " install");
146185
assertService(DEFAULT_ID, "Stopped");
147-
sh.run(serviceScript + " remove");
186+
sh.run(deleteCommand(DEFAULT_ID));
148187
}
149188

150189
// NOTE: service description is not attainable through any powershell api, so checking it is not possible...
151190
public void assertStartedAndStop() throws Exception {
152191
ServerUtils.waitForElasticsearch(installation);
153192
runElasticsearchTests();
154193

155-
assertCommand(serviceScript + " stop");
194+
assertCommand(stopCommand(DEFAULT_ID));
195+
waitForStop(DEFAULT_ID, Duration.ofMinutes(1));
156196
assertService(DEFAULT_ID, "Stopped");
157197
// the process is stopped async, and can become a zombie process, so we poll for the process actually being gone
158198
assertCommand(
@@ -170,36 +210,25 @@ public void assertStartedAndStop() throws Exception {
170210
+ "} while ($i -lt 300);"
171211
+ "exit 9;"
172212
);
173-
174-
assertCommand(serviceScript + " remove");
175-
assertCommand(
176-
"$p = Get-Service -Name \"elasticsearch-service-x64\" -ErrorAction SilentlyContinue;"
177-
+ "echo \"$p\";"
178-
+ "if ($p -eq $Null) {"
179-
+ " exit 0;"
180-
+ "} else {"
181-
+ " exit 1;"
182-
+ "}"
183-
);
184213
}
185214

186215
public void test30StartStop() throws Exception {
187216
sh.run(serviceScript + " install");
188-
assertCommand(serviceScript + " start");
217+
assertCommand(startCommand(DEFAULT_ID));
189218
assertStartedAndStop();
190219
}
191220

192221
public void test31StartNotInstalled() throws IOException {
193-
Result result = sh.runIgnoreExitCode(serviceScript + " start");
222+
Result result = sh.runIgnoreExitCode(startCommand(DEFAULT_ID));
194223
assertThat(result.stderr(), result.exitCode(), equalTo(1));
195224
dumpServiceLogs();
196-
assertThat(result.stderr(), containsString("Failed starting '" + DEFAULT_ID + "' service"));
225+
assertThat(result.stdout(), containsString("The specified service does not exist as an installed service"));
197226
}
198227

199228
public void test32StopNotStarted() throws IOException {
200229
sh.run(serviceScript + " install");
201-
Result result = sh.run(serviceScript + " stop"); // stop is ok when not started
202-
assertThat(result.stdout(), containsString("The service '" + DEFAULT_ID + "' has been stopped"));
230+
Result result = sh.runIgnoreExitCode(stopCommand(DEFAULT_ID));
231+
assertThat(result.stdout(), containsString("The service has not been started"));
203232
}
204233

205234
public void test33JavaChanged() throws Exception {
@@ -210,7 +239,7 @@ public void test33JavaChanged() throws Exception {
210239
sh.getEnv().put("ES_JAVA_HOME", alternateJdk.toString());
211240
assertCommand(serviceScript + " install");
212241
sh.getEnv().remove("ES_JAVA_HOME");
213-
assertCommand(serviceScript + " start");
242+
assertCommand(startCommand(DEFAULT_ID));
214243
assertStartedAndStop();
215244
} finally {
216245
FileUtils.rm(alternateJdk);
@@ -220,7 +249,7 @@ public void test33JavaChanged() throws Exception {
220249
public void test80JavaOptsInEnvVar() throws Exception {
221250
sh.getEnv().put("ES_JAVA_OPTS", "-Xmx2g -Xms2g");
222251
sh.run(serviceScript + " install");
223-
assertCommand(serviceScript + " start");
252+
assertCommand(startCommand(DEFAULT_ID));
224253
assertStartedAndStop();
225254
sh.getEnv().remove("ES_JAVA_OPTS");
226255
}
@@ -230,7 +259,7 @@ public void test81JavaOptsInJvmOptions() throws Exception {
230259
append(tempConf.resolve("jvm.options"), "-Xmx2g" + System.lineSeparator());
231260
append(tempConf.resolve("jvm.options"), "-Xms2g" + System.lineSeparator());
232261
sh.run(serviceScript + " install");
233-
assertCommand(serviceScript + " start");
262+
assertCommand(startCommand(DEFAULT_ID));
234263
assertStartedAndStop();
235264
});
236265
}

qa/packaging/src/test/java/org/elasticsearch/packaging/util/Platforms.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public static boolean isSystemd() {
5050
}
5151

5252
public static boolean isDocker() {
53+
if (WINDOWS) {
54+
return new Shell().runIgnoreExitCode("where docker").isSuccess();
55+
}
5356
return new Shell().runIgnoreExitCode("which docker").isSuccess();
5457
}
5558

0 commit comments

Comments
 (0)