This is an automated email from the ASF dual-hosted git repository. gortiz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push: new 2904ceb407 Delete database API (#12765) 2904ceb407 is described below commit 2904ceb4074ee222eeffa7d6806bd5e220f8d8a8 Author: Shounak kulkarni <shounakmk...@gmail.com> AuthorDate: Tue Apr 2 23:10:37 2024 +0500 Delete database API (#12765) * delete database API * add field for failed table deletion list along with cause of failure * remove _tablesInDatabase and _partiallyDeleted from DeleteDatabaseResponse * set default value for dryRun as true --- .../resources/PinotDatabaseRestletResource.java | 95 +++++++++++++++++++ .../PinotDatabaseRestletResourceTest.java | 102 +++++++++++++++++++++ .../java/org/apache/pinot/core/auth/Actions.java | 1 + 3 files changed, 198 insertions(+) diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResource.java index ac172bd3e5..e09829292c 100644 --- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResource.java +++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResource.java @@ -18,23 +18,31 @@ */ package org.apache.pinot.controller.api.resources; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.Api; import io.swagger.annotations.ApiKeyAuthDefinition; import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; +import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.core.auth.Actions; import org.apache.pinot.core.auth.Authorize; import org.apache.pinot.core.auth.TargetType; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,4 +67,91 @@ public class PinotDatabaseRestletResource { public List<String> listDatabaseNames() { return _pinotHelixResourceManager.getDatabaseNames(); } + + @DELETE + @Produces(MediaType.APPLICATION_JSON) + @Path("/databases/{databaseName}") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.DELETE_DATABASE) + @ApiOperation(value = "Delete all tables in given database name", notes = "Delete all tables in given database name") + public DeleteDatabaseResponse deleteTablesInDatabase( + @ApiParam(value = "Database name", required = true) @PathParam("databaseName") String databaseName, + @ApiParam(value = "Run in dryRun mode initially to know the list of tables that will be deleted in actual run. " + + "No tables will be deleted when dryRun=true", required = true, defaultValue = "true") + @QueryParam("dryRun") boolean dryRun) { + List<String> tablesInDatabase = _pinotHelixResourceManager.getAllTables(databaseName); + List<String> deletedTables = new ArrayList<>(tablesInDatabase.size()); + List<DeletionFailureWrapper> failedTables = new ArrayList<>(tablesInDatabase.size()); + if (dryRun) { + deletedTables.addAll(tablesInDatabase); + } else { + for (String table : tablesInDatabase) { + boolean isSchemaDeleted = false; + try { + TableType tableType = TableNameBuilder.getTableTypeFromTableName(table); + String rawTableName = TableNameBuilder.extractRawTableName(table); + _pinotHelixResourceManager.deleteSchema(rawTableName); + LOGGER.info("Deleted schema: {}", rawTableName); + isSchemaDeleted = true; + _pinotHelixResourceManager.deleteTable(table, tableType, null); + LOGGER.info("Deleted table: {}", table); + deletedTables.add(table); + } catch (Exception e) { + if (isSchemaDeleted) { + LOGGER.error("Failed to delete table {}", table); + } else { + LOGGER.error("Failed to delete table and schema for {}", table); + } + failedTables.add(new DeletionFailureWrapper(table, e.getMessage())); + } + } + } + return new DeleteDatabaseResponse(deletedTables, failedTables, dryRun); + } +} + +class DeleteDatabaseResponse { + private final List<String> _deletedTables; + private final List<DeletionFailureWrapper> _failedTables; + private final boolean _dryRun; + + public DeleteDatabaseResponse(List<String> deletedTables, List<DeletionFailureWrapper> failedTables, boolean dryRun) { + _deletedTables = deletedTables; + _failedTables = failedTables; + _dryRun = dryRun; + } + + @JsonProperty("deletedTables") + public List<String> getDeletedTables() { + return _deletedTables; + } + + @JsonProperty("failedTables") + public List<DeletionFailureWrapper> getFailedTables() { + return _failedTables; + } + + @JsonProperty("dryRun") + public boolean isDryRun() { + return _dryRun; + } +} + +class DeletionFailureWrapper { + private final String _tableName; + private final String _errorMessage; + + public DeletionFailureWrapper(String tableName, String errorMessage) { + _tableName = tableName; + _errorMessage = errorMessage; + } + + @JsonProperty("tableName") + public String getTableName() { + return _tableName; + } + + @JsonProperty("errorMessage") + public String getErrorMessage() { + return _errorMessage; + } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResourceTest.java new file mode 100644 index 0000000000..877c65f96b --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResourceTest.java @@ -0,0 +1,102 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.api.resources; + +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + + +public class PinotDatabaseRestletResourceTest { + private static final String DATABASE = "db"; + private static final List<String> TABLES = Lists.newArrayList("a_REALTIME", "b_OFFLINE", "c_REALTIME", "d_OFFLINE"); + + @Mock + PinotHelixResourceManager _resourceManager; + @InjectMocks + PinotDatabaseRestletResource _resource; + + @BeforeMethod + public void setup() { + MockitoAnnotations.openMocks(this); + when(_resourceManager.getAllTables(DATABASE)).thenReturn(TABLES); + doNothing().when(_resourceManager).deleteTable(anyString(), any(TableType.class), any()); + when(_resourceManager.deleteSchema(anyString())).thenReturn(true); + } + + @Test + public void successfulDatabaseDeletionDryRunTest() { + successfulDatabaseDeletionCheck(true); + } + + @Test + public void successfulDatabaseDeletionTest() { + successfulDatabaseDeletionCheck(false); + } + + private void successfulDatabaseDeletionCheck(boolean dryRun) { + DeleteDatabaseResponse response = _resource.deleteTablesInDatabase(DATABASE, dryRun); + assertEquals(response.isDryRun(), dryRun); + assertTrue(response.getFailedTables().isEmpty()); + assertEquals(response.getDeletedTables(), TABLES); + } + + @Test + public void partialDatabaseDeletionWithDeleteTableFailureTest() { + int failureTableIdx = TABLES.size() / 2; + doThrow(new RuntimeException()).when(_resourceManager) + .deleteTable(TABLES.get(failureTableIdx), TableType.REALTIME, null); + partialDatabaseDeletionCheck(failureTableIdx); + } + + @Test + public void partialDatabaseDeletionWithDeleteSchemaFailureTest() { + int failureSchemaIdx = TABLES.size() / 2; + doThrow(new RuntimeException()).when(_resourceManager) + .deleteSchema(TableNameBuilder.extractRawTableName(TABLES.get(failureSchemaIdx))); + partialDatabaseDeletionCheck(failureSchemaIdx); + } + + private void partialDatabaseDeletionCheck(int idx) { + DeleteDatabaseResponse response = _resource.deleteTablesInDatabase(DATABASE, false); + List<String> resultList = new ArrayList<>(TABLES); + String failedTable = resultList.remove(idx); + assertFalse(response.isDryRun()); + assertEquals(response.getFailedTables().size(), 1); + assertEquals(response.getFailedTables().get(0).getTableName(), failedTable); + assertEquals(response.getDeletedTables(), resultList); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java index 3a3e4ac593..3793d773a6 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java @@ -40,6 +40,7 @@ public class Actions { public static final String DELETE_TENANT = "DeleteTenant"; public static final String DELETE_USER = "DeleteUser"; public static final String DELETE_ZNODE = "DeleteZnode"; + public static final String DELETE_DATABASE = "DeleteDatabase"; public static final String ESTIMATE_UPSERT_MEMORY = "EstimateUpsertMemory"; public static final String EXECUTE_TASK = "ExecuteTask"; public static final String GET_ADMIN_INFO = "GetAdminInfo"; --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org