|
27 | 27 | import org.junit.AfterClass; |
28 | 28 | import org.junit.BeforeClass; |
29 | 29 |
|
| 30 | +import java.io.IOException; |
30 | 31 | import java.nio.file.FileStore; |
31 | 32 | import java.nio.file.FileSystem; |
32 | 33 | import java.nio.file.Path; |
|
40 | 41 | import java.util.concurrent.CountDownLatch; |
41 | 42 | import java.util.concurrent.TimeUnit; |
42 | 43 | import java.util.concurrent.atomic.AtomicLong; |
43 | | -import java.util.concurrent.atomic.AtomicReference; |
44 | 44 |
|
45 | 45 | import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule.ABORT; |
46 | 46 | import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule.BACKLOG; |
@@ -121,6 +121,7 @@ static class TestMockFileStore extends FileStore { |
121 | 121 | public volatile long totalSpace; |
122 | 122 | public volatile long freeSpace; |
123 | 123 | public volatile long usableSpace; |
| 124 | + public volatile boolean throwIoException; |
124 | 125 |
|
125 | 126 | private final String desc; |
126 | 127 |
|
@@ -149,17 +150,26 @@ public boolean isReadOnly() { |
149 | 150 | } |
150 | 151 |
|
151 | 152 | @Override |
152 | | - public long getTotalSpace() { |
| 153 | + public long getTotalSpace() throws IOException { |
| 154 | + if (throwIoException) { |
| 155 | + throw new IOException("Test IO Exception"); |
| 156 | + } |
153 | 157 | return totalSpace; |
154 | 158 | } |
155 | 159 |
|
156 | 160 | @Override |
157 | | - public long getUnallocatedSpace() { |
| 161 | + public long getUnallocatedSpace() throws IOException { |
| 162 | + if (throwIoException) { |
| 163 | + throw new IOException("Test IO Exception"); |
| 164 | + } |
158 | 165 | return freeSpace; |
159 | 166 | } |
160 | 167 |
|
161 | 168 | @Override |
162 | | - public long getUsableSpace() { |
| 169 | + public long getUsableSpace() throws IOException { |
| 170 | + if (throwIoException) { |
| 171 | + throw new IOException("Test IO Exception"); |
| 172 | + } |
163 | 173 | return usableSpace; |
164 | 174 | } |
165 | 175 |
|
@@ -190,24 +200,134 @@ public void testAvailableDiskSpaceMonitorWithDefaultSettings() throws Exception |
190 | 200 | aFileStore.totalSpace = aFileStore.usableSpace * 2; |
191 | 201 | bFileStore.usableSpace = 1_000L; |
192 | 202 | bFileStore.totalSpace = bFileStore.usableSpace * 2; |
193 | | - AtomicReference<ByteSizeValue> availableDiskSpaceForMerging = new AtomicReference<>(); |
194 | | - CountDownLatch diskSpaceMonitor = new CountDownLatch(1); |
| 203 | + LinkedHashSet<ByteSizeValue> availableDiskSpaceUpdates = new LinkedHashSet<>(); |
| 204 | + try ( |
| 205 | + var diskSpacePeriodicMonitor = ThreadPoolMergeExecutorService.startDiskSpaceMonitoring( |
| 206 | + testThreadPool, |
| 207 | + nodeEnvironment.dataPaths(), |
| 208 | + ClusterSettings.createBuiltInClusterSettings(settings), |
| 209 | + (availableDiskSpace) -> { |
| 210 | + synchronized (availableDiskSpaceUpdates) { |
| 211 | + availableDiskSpaceUpdates.add(availableDiskSpace); |
| 212 | + } |
| 213 | + } |
| 214 | + ) |
| 215 | + ) { |
| 216 | + assertBusy(() -> { |
| 217 | + synchronized (availableDiskSpaceUpdates) { |
| 218 | + assertThat(availableDiskSpaceUpdates.size(), is(1)); |
| 219 | + // 100_000 (available) - 5% (default flood stage level) * 200_000 (total space) |
| 220 | + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(90_000L)); |
| 221 | + } |
| 222 | + }); |
| 223 | + // "b" now has more available space |
| 224 | + bFileStore.usableSpace = 110_000L; |
| 225 | + bFileStore.totalSpace = 130_000L; |
| 226 | + assertBusy(() -> { |
| 227 | + synchronized (availableDiskSpaceUpdates) { |
| 228 | + assertThat(availableDiskSpaceUpdates.size(), is(2)); |
| 229 | + // 110_000 (available) - 5% (default flood stage level) * 130_000 (total space) |
| 230 | + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(103_500L)); |
| 231 | + } |
| 232 | + }); |
| 233 | + // available space for "a" and "b" is below the limit => it's clamp down to "0" |
| 234 | + aFileStore.usableSpace = 100L; |
| 235 | + bFileStore.usableSpace = 1_000L; |
| 236 | + assertBusy(() -> { |
| 237 | + synchronized (availableDiskSpaceUpdates) { |
| 238 | + assertThat(availableDiskSpaceUpdates.size(), is(3)); |
| 239 | + // 1_000 (available) - 5% (default flood stage level) * 130_000 (total space) < 0 |
| 240 | + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(0L)); |
| 241 | + } |
| 242 | + }); |
| 243 | + } |
| 244 | + } |
| 245 | + |
| 246 | + public void testAvailableDiskSpaceMonitorWhenFileSystemStatErrors() throws Exception { |
| 247 | + aFileStore.usableSpace = randomLongBetween(1L, 100L); |
| 248 | + aFileStore.totalSpace = randomLongBetween(1L, 100L); |
| 249 | + bFileStore.usableSpace = randomLongBetween(1L, 100L); |
| 250 | + bFileStore.totalSpace = randomLongBetween(1L, 100L); |
| 251 | + boolean aErrorsFirst = randomBoolean(); |
| 252 | + if (aErrorsFirst) { |
| 253 | + // the "a" file system will error when collecting stats |
| 254 | + aFileStore.throwIoException = true; |
| 255 | + bFileStore.throwIoException = false; |
| 256 | + } else { |
| 257 | + aFileStore.throwIoException = false; |
| 258 | + bFileStore.throwIoException = true; |
| 259 | + } |
| 260 | + LinkedHashSet<ByteSizeValue> availableDiskSpaceUpdates = new LinkedHashSet<>(); |
195 | 261 | try ( |
196 | 262 | var diskSpacePeriodicMonitor = ThreadPoolMergeExecutorService.startDiskSpaceMonitoring( |
197 | 263 | testThreadPool, |
198 | 264 | nodeEnvironment.dataPaths(), |
199 | 265 | ClusterSettings.createBuiltInClusterSettings(settings), |
200 | 266 | (availableDiskSpace) -> { |
201 | | - availableDiskSpaceForMerging.set(availableDiskSpace); |
202 | | - diskSpaceMonitor.countDown(); |
| 267 | + synchronized (availableDiskSpaceUpdates) { |
| 268 | + availableDiskSpaceUpdates.add(availableDiskSpace); |
| 269 | + } |
203 | 270 | } |
204 | 271 | ) |
205 | 272 | ) { |
206 | | - // wait for the disk space monitor to do a first run |
207 | | - safeAwait(diskSpaceMonitor); |
| 273 | + assertBusy(() -> { |
| 274 | + synchronized (availableDiskSpaceUpdates) { |
| 275 | + assertThat(availableDiskSpaceUpdates.size(), is(1)); |
| 276 | + if (aErrorsFirst) { |
| 277 | + // uses the stats from "b" |
| 278 | + assertThat( |
| 279 | + availableDiskSpaceUpdates.getLast().getBytes(), |
| 280 | + // the default 5% (same as flood stage level) |
| 281 | + is(Math.max(bFileStore.usableSpace - bFileStore.totalSpace / 20, 0L)) |
| 282 | + ); |
| 283 | + } else { |
| 284 | + // uses the stats from "a" |
| 285 | + assertThat( |
| 286 | + availableDiskSpaceUpdates.getLast().getBytes(), |
| 287 | + // the default 5% (same as flood stage level) |
| 288 | + is(Math.max(aFileStore.usableSpace - aFileStore.totalSpace / 20, 0L)) |
| 289 | + ); |
| 290 | + } |
| 291 | + } |
| 292 | + }); |
| 293 | + if (aErrorsFirst) { |
| 294 | + // the "b" file system will also now error when collecting stats |
| 295 | + bFileStore.throwIoException = true; |
| 296 | + } |
| 297 | + assertBusy(() -> { |
| 298 | + synchronized (availableDiskSpaceUpdates) { |
| 299 | + assertThat(availableDiskSpaceUpdates.size(), is(2)); |
| 300 | + // consider the available disk space as unlimited when no fs stats can be collected |
| 301 | + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(Long.MAX_VALUE)); |
| 302 | + } |
| 303 | + }); |
| 304 | + if (aErrorsFirst) { |
| 305 | + // "a" fs stats collection recovered |
| 306 | + aFileStore.throwIoException = false; |
| 307 | + } |
| 308 | + assertBusy(() -> { |
| 309 | + synchronized (availableDiskSpaceUpdates) { |
| 310 | + assertThat(availableDiskSpaceUpdates.size(), is(3)); |
| 311 | + if (aErrorsFirst) { |
| 312 | + // uses the stats from "a" |
| 313 | + assertThat( |
| 314 | + availableDiskSpaceUpdates.getLast().getBytes(), |
| 315 | + // the default 5% (same as flood stage level) |
| 316 | + is(Math.max(aFileStore.usableSpace - aFileStore.totalSpace / 20, 0L)) |
| 317 | + ); |
| 318 | + } else { |
| 319 | + // uses the stats from "b" |
| 320 | + assertThat( |
| 321 | + availableDiskSpaceUpdates.getLast().getBytes(), |
| 322 | + // the default 5% (same as flood stage level) |
| 323 | + is(Math.max(bFileStore.usableSpace - bFileStore.totalSpace / 20, 0L)) |
| 324 | + ); |
| 325 | + } |
| 326 | + } |
| 327 | + }); |
208 | 328 | } |
209 | | - // 100_000 (available) - 5% (default flood stage level) * 200_000 (total space) |
210 | | - assertThat(availableDiskSpaceForMerging.get().getBytes(), is(90_000L)); |
| 329 | + aFileStore.throwIoException = false; |
| 330 | + bFileStore.throwIoException = false; |
211 | 331 | } |
212 | 332 |
|
213 | 333 | public void testAvailableDiskSpaceMonitorSettingsUpdate() throws Exception { |
|
0 commit comments