Copilot commented on code in PR #4640:
URL: https://github.com/apache/bookkeeper/pull/4640#discussion_r2252919469


##########
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieStatus.java:
##########
@@ -216,6 +222,15 @@ public BookieStatus parse(BufferedReader reader)
             if (status.layoutVersion == 1 && parts.length == 3) {
                 status.bookieMode = BookieMode.valueOf(parts[1]);
                 status.lastUpdateTime = Long.parseLong(parts[2].trim());
+                status.isManuallyModifiedToReadOnly = true;

Review Comment:
   Setting `isManuallyModifiedToReadOnly = true` for old format (3 parts) is 
incorrect. Old format indicates the state was set by disk monitoring, not 
manually, so this should be `false`.
   ```suggestion
                   status.isManuallyModifiedToReadOnly = false;
   ```



##########
bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieInitializationTest.java:
##########
@@ -1445,30 +1452,154 @@ public void 
testRetrieveBookieStatusWhenStatusFileIsCorrupted() throws Exception
             .setPersistBookieStatusEnabled(true)
             .setMetadataServiceUri(metadataServiceUri);
         // start a new bookie
-        BookieServer bookieServer = new BookieServer(
+        BookieServer bookieServer1 = new BookieServer(
                 conf, new TestBookieImpl(conf),
                 NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
                 new MockUncleanShutdownDetection());
-        bookieServer.start();
+        bookieServer1.start();
         // transition in to read only and persist the status on disk
-        Bookie bookie = (BookieImpl) bookieServer.getBookie();
-        assertFalse(bookie.isReadOnly());
-        bookie.getStateManager().transitionToReadOnlyMode().get();
-        assertTrue(bookie.isReadOnly());
+        Bookie bookie1 = (BookieImpl) bookieServer1.getBookie();
+        assertFalse(bookie1.isReadOnly());
+        bookie1.getStateManager().transitionToReadOnlyMode().get();
+        assertTrue(bookie1.isReadOnly());
         // corrupt status file
-        List<File> ledgerDirs = ((BookieImpl) 
bookie).getLedgerDirsManager().getAllLedgerDirs();
+        List<File> ledgerDirs = ((BookieImpl) 
bookie1).getLedgerDirsManager().getAllLedgerDirs();
         corruptFile(new File(ledgerDirs.get(0), BOOKIE_STATUS_FILENAME));
         corruptFile(new File(ledgerDirs.get(1), BOOKIE_STATUS_FILENAME));
         // restart the bookie should be in read only mode
-        bookieServer.shutdown();
-        bookieServer = new BookieServer(
+        bookieServer1.shutdown();
+        BookieServer bookieServer2 = new BookieServer(
                 conf, new TestBookieImpl(conf),
                 NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
                 new MockUncleanShutdownDetection());
-        bookieServer.start();
-        bookie = bookieServer.getBookie();
-        assertTrue(bookie.isReadOnly());
-        bookieServer.shutdown();
+        bookieServer2.start();
+        BookieImpl bookie2 = (BookieImpl) bookieServer2.getBookie();
+        assertTrue(bookie2.isReadOnly());
+        // After a disk check, the bookie should switch to writable mode 
because the disk usage is fine.
+        bookie2.dirsMonitor.check();
+        Awaitility.await().untilAsserted(() -> {
+            assertFalse(bookie2.isReadOnly());
+        });
+        bookieServer2.shutdown();
+    }
+
+
+    private void setBookieToReadOnly(Bookie bookie, boolean readOnly) throws 
Exception {
+        BookieStateReadOnlyService.ReadOnlyState state = new 
BookieStateReadOnlyService.ReadOnlyState();
+        state.setReadOnly(readOnly);
+        HttpServiceRequest request = new 
HttpServiceRequest(JsonUtil.toJson(state), HttpServer.Method.PUT, null);
+        BookieStateReadOnlyService service = new 
BookieStateReadOnlyService(bookie);
+        HttpServiceResponse response1 = service.handle(request);
+        assertEquals(HttpServer.StatusCode.OK.getValue(), 
response1.getStatusCode());
+    }
+
+    /**
+     * Verify: once the state is manually modified to read-only by Admin API, 
it should not be changed to writable
+     * by the monitor task.
+     * But it can be changed to read-only by monitor task if it was manually 
set to writable by Admin API.
+     */
+    @Test(timeout = 1000 * 30)
+    public void 
testRetrieveBookieStatusAdminModifiedWhenStatusFileIsCorrupted() throws 
Exception {
+        File[] tmpLedgerDirs = new File[3];
+        String[] filePath = new String[tmpLedgerDirs.length];
+        for (int i = 0; i < tmpLedgerDirs.length; i++) {
+            tmpLedgerDirs[i] = tmpDirs.createNew("bookie", "test" + i);
+            filePath[i] = tmpLedgerDirs[i].getPath();
+        }
+        final ServerConfiguration conf = 
TestBKConfiguration.newServerConfiguration();
+        conf.setJournalDirName(filePath[0])
+            .setLedgerDirNames(filePath)
+            .setReadOnlyModeEnabled(true)
+            .setPersistBookieStatusEnabled(true)
+            .setMetadataServiceUri(metadataServiceUri);
+        // start a new bookie
+        BookieServer bookieServer1 = new BookieServer(
+                conf, new TestBookieImpl(conf),
+                NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
+                new MockUncleanShutdownDetection());
+        bookieServer1.start();
+        // transition in to read only and persist the status on disk
+        Bookie bookie1 = (BookieImpl) bookieServer1.getBookie();
+        assertFalse(bookie1.isReadOnly());
+        setBookieToReadOnly(bookie1, true);
+        assertTrue(bookie1.isReadOnly());
+        // corrupt status file
+        List<File> ledgerDirs = ((BookieImpl) 
bookie1).getLedgerDirsManager().getAllLedgerDirs();
+        corruptFile(new File(ledgerDirs.get(0), BOOKIE_STATUS_FILENAME));
+        corruptFile(new File(ledgerDirs.get(1), BOOKIE_STATUS_FILENAME));
+        // restart the bookie should be in read only mode
+        bookieServer1.shutdown();
+        BookieServer bookieServer2 = new BookieServer(
+                conf, new TestBookieImpl(conf),
+                NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
+                new MockUncleanShutdownDetection());
+        bookieServer2.start();
+        BookieImpl bookie2 = (BookieImpl) bookieServer2.getBookie();
+        assertTrue(bookie2.isReadOnly());
+        // After a disk check, the bookie should not switch to writable mode 
because the state was set to read-only
+        // by admin api manually.
+        bookie2.dirsMonitor.check();
+        Thread.sleep(2000);

Review Comment:
   Using `Thread.sleep()` in tests is unreliable and can cause flaky tests. The 
`Awaitility.await()` call on the next line should be sufficient for waiting.
   ```suggestion
   
   ```



##########
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerDirsMonitor.java:
##########
@@ -175,6 +181,14 @@ private void check(final LedgerDirsManager ldm) {
             }
         }
 
+        if (isFirstLoopOfCheckTaskLocalValue && 
ldm.getFullFilledLedgerDirs().isEmpty()) {

Review Comment:
   The early return on line 189 bypasses all the existing disk monitoring logic 
below. Consider moving this check after the existing monitoring logic or 
documenting why the bypass is intentional.



##########
bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieInitializationTest.java:
##########
@@ -1445,30 +1452,154 @@ public void 
testRetrieveBookieStatusWhenStatusFileIsCorrupted() throws Exception
             .setPersistBookieStatusEnabled(true)
             .setMetadataServiceUri(metadataServiceUri);
         // start a new bookie
-        BookieServer bookieServer = new BookieServer(
+        BookieServer bookieServer1 = new BookieServer(
                 conf, new TestBookieImpl(conf),
                 NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
                 new MockUncleanShutdownDetection());
-        bookieServer.start();
+        bookieServer1.start();
         // transition in to read only and persist the status on disk
-        Bookie bookie = (BookieImpl) bookieServer.getBookie();
-        assertFalse(bookie.isReadOnly());
-        bookie.getStateManager().transitionToReadOnlyMode().get();
-        assertTrue(bookie.isReadOnly());
+        Bookie bookie1 = (BookieImpl) bookieServer1.getBookie();
+        assertFalse(bookie1.isReadOnly());
+        bookie1.getStateManager().transitionToReadOnlyMode().get();
+        assertTrue(bookie1.isReadOnly());
         // corrupt status file
-        List<File> ledgerDirs = ((BookieImpl) 
bookie).getLedgerDirsManager().getAllLedgerDirs();
+        List<File> ledgerDirs = ((BookieImpl) 
bookie1).getLedgerDirsManager().getAllLedgerDirs();
         corruptFile(new File(ledgerDirs.get(0), BOOKIE_STATUS_FILENAME));
         corruptFile(new File(ledgerDirs.get(1), BOOKIE_STATUS_FILENAME));
         // restart the bookie should be in read only mode
-        bookieServer.shutdown();
-        bookieServer = new BookieServer(
+        bookieServer1.shutdown();
+        BookieServer bookieServer2 = new BookieServer(
                 conf, new TestBookieImpl(conf),
                 NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
                 new MockUncleanShutdownDetection());
-        bookieServer.start();
-        bookie = bookieServer.getBookie();
-        assertTrue(bookie.isReadOnly());
-        bookieServer.shutdown();
+        bookieServer2.start();
+        BookieImpl bookie2 = (BookieImpl) bookieServer2.getBookie();
+        assertTrue(bookie2.isReadOnly());
+        // After a disk check, the bookie should switch to writable mode 
because the disk usage is fine.
+        bookie2.dirsMonitor.check();
+        Awaitility.await().untilAsserted(() -> {
+            assertFalse(bookie2.isReadOnly());
+        });
+        bookieServer2.shutdown();
+    }
+
+
+    private void setBookieToReadOnly(Bookie bookie, boolean readOnly) throws 
Exception {
+        BookieStateReadOnlyService.ReadOnlyState state = new 
BookieStateReadOnlyService.ReadOnlyState();
+        state.setReadOnly(readOnly);
+        HttpServiceRequest request = new 
HttpServiceRequest(JsonUtil.toJson(state), HttpServer.Method.PUT, null);
+        BookieStateReadOnlyService service = new 
BookieStateReadOnlyService(bookie);
+        HttpServiceResponse response1 = service.handle(request);
+        assertEquals(HttpServer.StatusCode.OK.getValue(), 
response1.getStatusCode());
+    }
+
+    /**
+     * Verify: once the state is manually modified to read-only by Admin API, 
it should not be changed to writable
+     * by the monitor task.
+     * But it can be changed to read-only by monitor task if it was manually 
set to writable by Admin API.
+     */
+    @Test(timeout = 1000 * 30)
+    public void 
testRetrieveBookieStatusAdminModifiedWhenStatusFileIsCorrupted() throws 
Exception {
+        File[] tmpLedgerDirs = new File[3];
+        String[] filePath = new String[tmpLedgerDirs.length];
+        for (int i = 0; i < tmpLedgerDirs.length; i++) {
+            tmpLedgerDirs[i] = tmpDirs.createNew("bookie", "test" + i);
+            filePath[i] = tmpLedgerDirs[i].getPath();
+        }
+        final ServerConfiguration conf = 
TestBKConfiguration.newServerConfiguration();
+        conf.setJournalDirName(filePath[0])
+            .setLedgerDirNames(filePath)
+            .setReadOnlyModeEnabled(true)
+            .setPersistBookieStatusEnabled(true)
+            .setMetadataServiceUri(metadataServiceUri);
+        // start a new bookie
+        BookieServer bookieServer1 = new BookieServer(
+                conf, new TestBookieImpl(conf),
+                NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
+                new MockUncleanShutdownDetection());
+        bookieServer1.start();
+        // transition in to read only and persist the status on disk
+        Bookie bookie1 = (BookieImpl) bookieServer1.getBookie();
+        assertFalse(bookie1.isReadOnly());
+        setBookieToReadOnly(bookie1, true);
+        assertTrue(bookie1.isReadOnly());
+        // corrupt status file
+        List<File> ledgerDirs = ((BookieImpl) 
bookie1).getLedgerDirsManager().getAllLedgerDirs();
+        corruptFile(new File(ledgerDirs.get(0), BOOKIE_STATUS_FILENAME));
+        corruptFile(new File(ledgerDirs.get(1), BOOKIE_STATUS_FILENAME));
+        // restart the bookie should be in read only mode
+        bookieServer1.shutdown();
+        BookieServer bookieServer2 = new BookieServer(
+                conf, new TestBookieImpl(conf),
+                NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
+                new MockUncleanShutdownDetection());
+        bookieServer2.start();
+        BookieImpl bookie2 = (BookieImpl) bookieServer2.getBookie();
+        assertTrue(bookie2.isReadOnly());
+        // After a disk check, the bookie should not switch to writable mode 
because the state was set to read-only
+        // by admin api manually.
+        bookie2.dirsMonitor.check();
+        Thread.sleep(2000);
+        Awaitility.await().untilAsserted(() -> {
+            assertTrue(bookie2.isReadOnly());
+        });
+        // We can use Admin Api to change the state back to writable.
+        setBookieToReadOnly(bookie2, false);
+        assertFalse(bookie2.isReadOnly());
+        // The state can be changed to read-only by monitor task if it was 
manually set to writable by Admin API.
+        bookie2.getStateManager().transitionToReadOnlyMode().get();
+        assertTrue(bookie2.isReadOnly());
+        bookieServer2.shutdown();
+    }
+
+    /**
+     * Verify: the newest version can read the old version payload of 
persisted bookie status.
+     * old payload: "1,READ_ONLY,1752809349613"
+     * new payload: "1,READ_ONLY,1752809349613,false"
+     */
+    @Test(timeout = 10000)
+    public void testPersistBookieStatusCompatibility() throws Exception {
+        File[] tmpLedgerDirs = new File[1];
+        String[] filePath = new String[1];
+        tmpLedgerDirs[0] = tmpDirs.createNew("bookie", "test");
+        filePath[0] = tmpLedgerDirs[0].getPath();
+        final ServerConfiguration conf = 
TestBKConfiguration.newServerConfiguration();
+        conf.setJournalDirName(filePath[0])
+                .setLedgerDirNames(filePath)
+                .setReadOnlyModeEnabled(true)
+                .setPersistBookieStatusEnabled(true)
+                .setMetadataServiceUri(metadataServiceUri);
+        // start a new bookie
+        BookieServer bookieServer1 = new BookieServer(
+                conf, new TestBookieImpl(conf),
+                NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
+                new MockUncleanShutdownDetection());
+        bookieServer1.start();
+        // transition in to read only and persist the status on disk
+        Bookie bookie1 = (BookieImpl) bookieServer1.getBookie();
+        assertFalse(bookie1.isReadOnly());
+        bookie1.getStateManager().transitionToReadOnlyMode().get();
+        assertTrue(bookie1.isReadOnly());
+        // Rewrite file to the old payload.
+        List<File> ledgerDirs = ((BookieImpl) 
bookie1).getLedgerDirsManager().getAllLedgerDirs();
+        FileUtils.writeStringToFile(new File(ledgerDirs.get(0), 
BOOKIE_STATUS_FILENAME),
+                "1,READ_ONLY,1752809349613");
+        // restart the bookie should be in read only mode
+        bookieServer1.shutdown();
+        BookieServer bookieServer2 = new BookieServer(
+                conf, new TestBookieImpl(conf),
+                NullStatsLogger.INSTANCE, UnpooledByteBufAllocator.DEFAULT,
+                new MockUncleanShutdownDetection());
+        bookieServer2.start();
+        BookieImpl bookie2 = (BookieImpl) bookieServer2.getBookie();
+        assertTrue(bookie2.isReadOnly());
+        // After a disk check, the bookie should not switch to writable mode 
because the state was set to read-only
+        // by admin api manually.

Review Comment:
   The comment says the bookie should NOT switch to writable mode, but the test 
assertion on line 1600 expects it to switch to writable mode 
(`assertFalse(bookie2.isReadOnly())`). The comment should say 'should switch to 
writable mode'.
   ```suggestion
           // After a disk check, the bookie should switch to writable mode 
because the persisted state is compatible
           // and the disk is healthy.
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to