diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java index e52fd87560d..441833bd49d 100644 --- a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java +++ b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java @@ -46,6 +46,7 @@ import org.apache.gravitino.dto.MetalakeDTO; import org.apache.gravitino.dto.authorization.SecurableObjectDTO; import org.apache.gravitino.dto.requests.CatalogCreateRequest; +import org.apache.gravitino.dto.requests.CatalogSetRequest; import org.apache.gravitino.dto.requests.CatalogUpdateRequest; import org.apache.gravitino.dto.requests.CatalogUpdatesRequest; import org.apache.gravitino.dto.requests.GroupAddRequest; @@ -297,9 +298,12 @@ public boolean dropCatalog(String catalogName, boolean force) @Override public void enableCatalog(String catalogName) throws NoSuchCatalogException { + CatalogSetRequest req = new CatalogSetRequest(true); + ErrorResponse resp = - restClient.get( - String.format(API_METALAKES_CATALOGS_PATH, this.name(), catalogName) + "/activate", + restClient.patch( + String.format(API_METALAKES_CATALOGS_PATH, this.name(), catalogName), + req, ErrorResponse.class, Collections.emptyMap(), ErrorHandlers.catalogErrorHandler()); @@ -313,9 +317,12 @@ public void enableCatalog(String catalogName) throws NoSuchCatalogException { @Override public void disableCatalog(String catalogName) throws NoSuchCatalogException { + CatalogSetRequest req = new CatalogSetRequest(false); + ErrorResponse resp = - restClient.get( - String.format(API_METALAKES_CATALOGS_PATH, this.name(), catalogName) + "/disable", + restClient.patch( + String.format(API_METALAKES_CATALOGS_PATH, this.name(), catalogName), + req, ErrorResponse.class, Collections.emptyMap(), ErrorHandlers.catalogErrorHandler()); diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/HTTPClient.java b/clients/client-java/src/main/java/org/apache/gravitino/client/HTTPClient.java index 3dfc8c551f0..058d04c82d3 100644 --- a/clients/client-java/src/main/java/org/apache/gravitino/client/HTTPClient.java +++ b/clients/client-java/src/main/java/org/apache/gravitino/client/HTTPClient.java @@ -561,6 +561,31 @@ public T put( Method.PUT, path, null, body, responseType, headers, errorHandler, responseHeaders); } + /** + * Sends an HTTP PATCH request to the specified path with the provided request body and processes + * the response with support for response headers. + * + * @param path The URL path to send the PATCH request to. + * @param body The REST request to place in the request body. + * @param responseType The class type of the response for deserialization (Must be registered with + * the ObjectMapper). + * @param headers A map of request headers (key-value pairs) to include in the request (can be + * null). + * @param errorHandler The error handler delegated for HTTP responses, which handles server error + * responses. + * @return The response entity parsed and converted to its type T. + * @param The class type of the response for deserialization. + */ + @Override + public T patch( + String path, + RESTRequest body, + Class responseType, + Map headers, + Consumer errorHandler) { + return execute(Method.PATCH, path, null, body, responseType, headers, errorHandler); + } + /** * Sends an HTTP DELETE request to the specified path without query parameters and processes the * response. diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/RESTClient.java b/clients/client-java/src/main/java/org/apache/gravitino/client/RESTClient.java index 191db5f8d9d..384a6358231 100644 --- a/clients/client-java/src/main/java/org/apache/gravitino/client/RESTClient.java +++ b/clients/client-java/src/main/java/org/apache/gravitino/client/RESTClient.java @@ -379,6 +379,24 @@ T put( Map headers, Consumer errorHandler); + /** + * Perform a PATCH request on the specified path with given information. + * + * @param path The path to be requested. + * @param body The request body to be included in the PATCH request. + * @param responseType The class representing the type of the response. + * @param headers The headers to be included in the request. + * @param errorHandler The consumer for handling error responses. + * @return The response of the PATCH request. + * @param The type of the response. + */ + T patch( + String path, + RESTRequest body, + Class responseType, + Map headers, + Consumer errorHandler); + /** * Perform a POST request with form data on the specified path with the given information. * diff --git a/clients/client-python/gravitino/client/gravitino_metalake.py b/clients/client-python/gravitino/client/gravitino_metalake.py index 83005ebd905..c47412afb9e 100644 --- a/clients/client-python/gravitino/client/gravitino_metalake.py +++ b/clients/client-python/gravitino/client/gravitino_metalake.py @@ -23,6 +23,7 @@ from gravitino.dto.dto_converters import DTOConverters from gravitino.dto.metalake_dto import MetalakeDTO from gravitino.dto.requests.catalog_create_request import CatalogCreateRequest +from gravitino.dto.requests.catalog_set_request import CatalogSetRequest from gravitino.dto.requests.catalog_updates_request import CatalogUpdatesRequest from gravitino.dto.responses.catalog_list_response import CatalogListResponse from gravitino.dto.responses.catalog_response import CatalogResponse @@ -215,8 +216,14 @@ def enable_catalog(self, name: str): Raises: NoSuchCatalogException if the catalog with specified name does not exist. """ - url = self.API_METALAKES_CATALOGS_PATH.format(self.name(), name) + "/enable" - self.rest_client.get(url, error_handler=CATALOG_ERROR_HANDLER) + + catalog_enable_request = CatalogSetRequest(in_use=True) + catalog_enable_request.validate() + + url = self.API_METALAKES_CATALOGS_PATH.format(self.name(), name) + self.rest_client.patch( + url, json=catalog_enable_request, error_handler=CATALOG_ERROR_HANDLER + ) def disable_catalog(self, name: str): """Disable the catalog with specified name. If the catalog is already disabled, this method does nothing. @@ -227,5 +234,11 @@ def disable_catalog(self, name: str): Raises: NoSuchCatalogException if the catalog with specified name does not exist. """ - url = self.API_METALAKES_CATALOGS_PATH.format(self.name(), name) + "/disable" - self.rest_client.get(url, error_handler=CATALOG_ERROR_HANDLER) + + catalog_disable_request = CatalogSetRequest(in_use=False) + catalog_disable_request.validate() + + url = self.API_METALAKES_CATALOGS_PATH.format(self.name(), name) + self.rest_client.patch( + url, json=catalog_disable_request, error_handler=CATALOG_ERROR_HANDLER + ) diff --git a/clients/client-python/gravitino/dto/requests/catalog_set_request.py b/clients/client-python/gravitino/dto/requests/catalog_set_request.py new file mode 100644 index 00000000000..b93d8bd0f30 --- /dev/null +++ b/clients/client-python/gravitino/dto/requests/catalog_set_request.py @@ -0,0 +1,41 @@ +# 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. + +from dataclasses import dataclass, field + +from dataclasses_json import config + +from gravitino.rest.rest_message import RESTRequest + + +@dataclass +class CatalogSetRequest(RESTRequest): + """Represents a request to set a catalog in use.""" + + _in_use: bool = field(metadata=config(field_name="inUse")) + + def __init__(self, in_use: bool): + self._in_use = in_use + + def validate(self): + """Validates the fields of the request. + + Raises: + IllegalArgumentException if in_use is not set. + """ + if self._in_use is None: + raise ValueError('"in_use" field is required and cannot be empty') diff --git a/clients/client-python/gravitino/utils/http_client.py b/clients/client-python/gravitino/utils/http_client.py index 262c73c2b4c..b3e38a5d359 100644 --- a/clients/client-python/gravitino/utils/http_client.py +++ b/clients/client-python/gravitino/utils/http_client.py @@ -240,6 +240,11 @@ def put(self, endpoint, json=None, error_handler=None, **kwargs): "put", endpoint, json=json, error_handler=error_handler, **kwargs ) + def patch(self, endpoint, json=None, error_handler=None, **kwargs): + return self._request( + "patch", endpoint, json=json, error_handler=error_handler, **kwargs + ) + def post_form(self, endpoint, data=None, error_handler=None, **kwargs): return self._request( "post", endpoint, data=data, error_handler=error_handler, **kwargs diff --git a/clients/client-python/tests/integration/test_catalog.py b/clients/client-python/tests/integration/test_catalog.py index 37e8aa233c4..58580b8c518 100644 --- a/clients/client-python/tests/integration/test_catalog.py +++ b/clients/client-python/tests/integration/test_catalog.py @@ -145,9 +145,8 @@ def test_alter_catalog(self): def test_drop_catalog(self): self.create_catalog(self.catalog_name) - self.assertTrue( - self.gravitino_client.drop_catalog(name=self.catalog_name, force=True) - ) + self.gravitino_client.disable_catalog(self.catalog_name) + self.assertTrue(self.gravitino_client.drop_catalog(name=self.catalog_name)) def test_list_catalogs_info(self): self.create_catalog(self.catalog_name) diff --git a/common/src/main/java/org/apache/gravitino/dto/requests/CatalogSetRequest.java b/common/src/main/java/org/apache/gravitino/dto/requests/CatalogSetRequest.java new file mode 100644 index 00000000000..d2c9c744b2f --- /dev/null +++ b/common/src/main/java/org/apache/gravitino/dto/requests/CatalogSetRequest.java @@ -0,0 +1,59 @@ +/* + * 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.gravitino.dto.requests; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.apache.gravitino.rest.RESTRequest; + +/** Represents a request to set a catalog in use. */ +@Getter +@EqualsAndHashCode +@ToString +public class CatalogSetRequest implements RESTRequest { + + @JsonProperty("inUse") + private final boolean inUse; + + /** Default constructor for CatalogSetRequest. */ + public CatalogSetRequest() { + this(false); + } + + /** + * Constructor for CatalogSetRequest. + * + * @param inUse The in use status to set. + */ + public CatalogSetRequest(boolean inUse) { + this.inUse = inUse; + } + + /** + * Validates the request. No validation needed. + * + * @throws IllegalArgumentException If the request is invalid. + */ + @Override + public void validate() throws IllegalArgumentException { + // No validation needed + } +} diff --git a/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java b/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java index 11544ba9383..ba934d5bdcf 100644 --- a/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java +++ b/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java @@ -26,6 +26,7 @@ import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.PATCH; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -41,6 +42,7 @@ import org.apache.gravitino.Namespace; import org.apache.gravitino.catalog.CatalogDispatcher; import org.apache.gravitino.dto.requests.CatalogCreateRequest; +import org.apache.gravitino.dto.requests.CatalogSetRequest; import org.apache.gravitino.dto.requests.CatalogUpdateRequest; import org.apache.gravitino.dto.requests.CatalogUpdatesRequest; import org.apache.gravitino.dto.responses.BaseResponse; @@ -187,14 +189,16 @@ public Response testConnection( } } - @GET - @Path("{catalog}/enable") + @PATCH + @Path("{catalog}") @Produces("application/vnd.gravitino.v1+json") - @Timed(name = "enable-catalog." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) - @ResponseMetered(name = "enable-catalog", absolute = true) - public Response enableCatalog( - @PathParam("metalake") String metalake, @PathParam("catalog") String catalogName) { - LOG.info("Received enable request for catalog: {}.{}", metalake, catalogName); + @Timed(name = "set-catalog." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) + @ResponseMetered(name = "set-catalog", absolute = true) + public Response setCatalog( + @PathParam("metalake") String metalake, + @PathParam("catalog") String catalogName, + CatalogSetRequest request) { + LOG.info("Received set request for catalog: {}.{}", metalake, catalogName); try { return Utils.doAs( httpRequest, @@ -204,53 +208,33 @@ public Response enableCatalog( NameIdentifierUtil.ofMetalake(metalake), LockType.WRITE, () -> { - catalogDispatcher.enableCatalog(ident); + if (request.isInUse()) { + catalogDispatcher.enableCatalog(ident); + } else { + catalogDispatcher.disableCatalog(ident); + } return null; }); Response response = Utils.ok(new BaseResponse()); - LOG.info("Successfully enable catalog: {}.{}", metalake, catalogName); + LOG.info( + "Successfully {} catalog: {}.{}", + request.isInUse() ? "enable" : "disable", + metalake, + catalogName); return response; }); } catch (Exception e) { - LOG.info("Failed to enable catalog: {}.{}", metalake, catalogName); + LOG.info( + "Failed to {} catalog: {}.{}", + request.isInUse() ? "enable" : "disable", + metalake, + catalogName); return ExceptionHandlers.handleCatalogException( OperationType.ENABLE, catalogName, metalake, e); } } - @GET - @Path("{catalog}/disable") - @Produces("disable/vnd.gravitino.v1+json") - @Timed(name = "disable-catalog." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) - @ResponseMetered(name = "disable-catalog", absolute = true) - public Response disableCatalog( - @PathParam("metalake") String metalake, @PathParam("catalog") String catalogName) { - LOG.info("Received disable request for catalog: {}.{}", metalake, catalogName); - try { - return Utils.doAs( - httpRequest, - () -> { - NameIdentifier ident = NameIdentifierUtil.ofCatalog(metalake, catalogName); - TreeLockUtils.doWithTreeLock( - NameIdentifierUtil.ofMetalake(metalake), - LockType.WRITE, - () -> { - catalogDispatcher.disableCatalog(ident); - return null; - }); - Response response = Utils.ok(new BaseResponse()); - LOG.info("Successfully disable catalog: {}.{}", metalake, catalogName); - return response; - }); - - } catch (Exception e) { - LOG.info("Failed to disable catalog: {}.{}", metalake, catalogName); - return ExceptionHandlers.handleCatalogException( - OperationType.DISABLE, catalogName, metalake, e); - } - } - @GET @Path("{catalog}") @Produces("application/vnd.gravitino.v1+json") diff --git a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java index a2a128c8bc8..9b8c8609336 100644 --- a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java +++ b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java @@ -50,6 +50,7 @@ import org.apache.gravitino.catalog.CatalogManager; import org.apache.gravitino.dto.CatalogDTO; import org.apache.gravitino.dto.requests.CatalogCreateRequest; +import org.apache.gravitino.dto.requests.CatalogSetRequest; import org.apache.gravitino.dto.requests.CatalogUpdateRequest; import org.apache.gravitino.dto.requests.CatalogUpdatesRequest; import org.apache.gravitino.dto.responses.BaseResponse; @@ -67,6 +68,7 @@ import org.apache.gravitino.meta.CatalogEntity; import org.apache.gravitino.rest.RESTUtils; import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.TestProperties; @@ -517,6 +519,37 @@ public void testDropCatalog() { Assertions.assertEquals(RuntimeException.class.getSimpleName(), errorResponse.getType()); } + @Test + public void testSetCatalog() { + CatalogSetRequest req = new CatalogSetRequest(true); + doNothing().when(manager).enableCatalog(any()); + + Response resp = + target("/metalakes/metalake1/catalogs/catalog1") + .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) + .request(MediaType.APPLICATION_JSON_TYPE) + .accept("application/vnd.gravitino.v1+json") + .method("PATCH", Entity.entity(req, MediaType.APPLICATION_JSON_TYPE)); + + Assertions.assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus()); + BaseResponse baseResponse = resp.readEntity(BaseResponse.class); + Assertions.assertEquals(0, baseResponse.getCode()); + + req = new CatalogSetRequest(false); + doNothing().when(manager).disableCatalog(any()); + + resp = + target("/metalakes/metalake1/catalogs/catalog1") + .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) + .request(MediaType.APPLICATION_JSON_TYPE) + .accept("application/vnd.gravitino.v1+json") + .method("PATCH", Entity.entity(req, MediaType.APPLICATION_JSON_TYPE)); + + Assertions.assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus()); + baseResponse = resp.readEntity(BaseResponse.class); + Assertions.assertEquals(0, baseResponse.getCode()); + } + private static TestCatalog buildCatalog(String metalake, String catalogName) { CatalogEntity entity = CatalogEntity.builder()