This is an automated email from the ASF dual-hosted git repository.
liuxun pushed a commit to branch branch-0.6
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-0.6 by this push:
new 9f2b4f720 [#4295] feat(server): Add REST API for the owner (#4385)
9f2b4f720 is described below
commit 9f2b4f720bbb1373822d4a233da01e83024da888
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Aug 6 17:16:01 2024 +0800
[#4295] feat(server): Add REST API for the owner (#4385)
### What changes were proposed in this pull request?
Add REST API for the owner.
### Why are the changes needed?
Fix: #4295
### Does this PR introduce _any_ user-facing change?
I will add the document in the later pull request.
### How was this patch tested?
Add the new ut.
Co-authored-by: roryqi <[email protected]>
---
.../gravitino/dto/authorization/OwnerDTO.java | 93 +++++++++
.../gravitino/dto/requests/OwnerSetRequest.java | 67 +++++++
.../gravitino/dto/responses/OwnerResponse.java | 66 +++++++
.../gravitino/dto/responses/SetResponse.java | 57 ++++++
.../apache/gravitino/dto/util/DTOConverters.java | 12 ++
.../java/org/apache/gravitino/GravitinoEnv.java | 1 +
.../server/web/rest/ExceptionHandlers.java | 32 +++
.../gravitino/server/web/rest/OperationType.java | 1 +
.../gravitino/server/web/rest/OwnerOperations.java | 116 +++++++++++
.../server/web/rest/TestOwnerOperations.java | 220 +++++++++++++++++++++
10 files changed, 665 insertions(+)
diff --git
a/common/src/main/java/org/apache/gravitino/dto/authorization/OwnerDTO.java
b/common/src/main/java/org/apache/gravitino/dto/authorization/OwnerDTO.java
new file mode 100644
index 000000000..e2c74433f
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/authorization/OwnerDTO.java
@@ -0,0 +1,93 @@
+/*
+ * 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.authorization;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.gravitino.authorization.Owner;
+
+/** Represents an Owner Data Transfer Object (DTO). */
+public class OwnerDTO implements Owner {
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("type")
+ private Type type;
+
+ private OwnerDTO() {}
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Creates a new Builder for constructing an Owner DTO.
+ *
+ * @return A new Builder instance.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** Builder class for constructing OwnerDTO instances. */
+ public static class Builder {
+
+ private final OwnerDTO ownerDTO;
+
+ private Builder() {
+ ownerDTO = new OwnerDTO();
+ }
+
+ /**
+ * Sets the name for the owner.
+ *
+ * @param name The name of the owner.
+ * @return The builder instance.
+ */
+ public Builder withName(String name) {
+ ownerDTO.name = name;
+ return this;
+ }
+
+ /**
+ * Sets the type for the owner.
+ *
+ * @param type The type of the owner.
+ * @return The builder instance.
+ */
+ public Builder withType(Type type) {
+ ownerDTO.type = type;
+ return this;
+ }
+
+ /**
+ * Builds an instance of OwnerDTO using the builder's properties.
+ *
+ * @return An instance of OwnerDTO.
+ */
+ public OwnerDTO build() {
+ return ownerDTO;
+ }
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/OwnerSetRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/OwnerSetRequest.java
new file mode 100644
index 000000000..2a10904c6
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/OwnerSetRequest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.google.common.base.Preconditions;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.extern.jackson.Jacksonized;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Request to set the owner for a metadata object. */
+@Getter
+@EqualsAndHashCode
+@ToString
+@Builder
+@Jacksonized
+public class OwnerSetRequest implements RESTRequest {
+ @JsonProperty("name")
+ private final String name;
+
+ @JsonProperty("type")
+ private final Owner.Type type;
+
+ /** Default constructor for OwnerSetRequest. (Used for Jackson
deserialization.) */
+ public OwnerSetRequest() {
+ this(null, null);
+ }
+
+ /**
+ * Creates a new OwnerSetRequest.
+ *
+ * @param name The name of the owner.
+ * @param type The type of the owner.
+ */
+ public OwnerSetRequest(String name, Owner.Type type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(name), "\"name\" field is required and cannot
be empty");
+ Preconditions.checkArgument(type != null, "\"type\" field is required and
cannot be empty");
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/responses/OwnerResponse.java
b/common/src/main/java/org/apache/gravitino/dto/responses/OwnerResponse.java
new file mode 100644
index 000000000..cf4569f5a
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/responses/OwnerResponse.java
@@ -0,0 +1,66 @@
+/*
+ * 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.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import javax.annotation.Nullable;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.authorization.OwnerDTO;
+
+/** Represents a response containing owner information. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class OwnerResponse extends BaseResponse {
+
+ @Nullable
+ @JsonProperty("owner")
+ private final OwnerDTO owner;
+
+ /**
+ * Constructor for OwnerResponse.
+ *
+ * @param owner The owner data transfer object.
+ */
+ public OwnerResponse(OwnerDTO owner) {
+ super(0);
+ this.owner = owner;
+ }
+
+ /** Default constructor for OwnerResponse. (Used for Jackson
deserialization.) */
+ public OwnerResponse() {
+ super(0);
+ this.owner = null;
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ super.validate();
+
+ if (owner != null) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(owner.name()), "owner 'name' must not be null
or empty");
+ Preconditions.checkArgument(owner.type() != null, "owner 'type' must not
be null");
+ }
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/responses/SetResponse.java
b/common/src/main/java/org/apache/gravitino/dto/responses/SetResponse.java
new file mode 100644
index 000000000..52429ceb6
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/responses/SetResponse.java
@@ -0,0 +1,57 @@
+/*
+ * 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.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/** Represents a response for a set operation. */
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class SetResponse extends BaseResponse {
+
+ @JsonProperty("set")
+ private final boolean set;
+
+ /**
+ * Constructor for SetResponse.
+ *
+ * @param set Whether the set operation was successful.
+ */
+ public SetResponse(boolean set) {
+ super(0);
+ this.set = set;
+ }
+
+ /** Default constructor for SetResponse (used by Jackson deserializer). */
+ public SetResponse() {
+ super(0);
+ this.set = false;
+ }
+
+ /**
+ * Returns whether the set operation was successful.
+ *
+ * @return True if the set operation was successful, otherwise false.
+ */
+ public boolean set() {
+ return set;
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
index 9588fae8b..d83460af1 100644
--- a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
+++ b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
@@ -30,6 +30,7 @@ import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.Metalake;
import org.apache.gravitino.Schema;
import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.Owner;
import org.apache.gravitino.authorization.Privilege;
import org.apache.gravitino.authorization.Role;
import org.apache.gravitino.authorization.SecurableObject;
@@ -39,6 +40,7 @@ import org.apache.gravitino.dto.CatalogDTO;
import org.apache.gravitino.dto.MetalakeDTO;
import org.apache.gravitino.dto.SchemaDTO;
import org.apache.gravitino.dto.authorization.GroupDTO;
+import org.apache.gravitino.dto.authorization.OwnerDTO;
import org.apache.gravitino.dto.authorization.PrivilegeDTO;
import org.apache.gravitino.dto.authorization.RoleDTO;
import org.apache.gravitino.dto.authorization.SecurableObjectDTO;
@@ -118,6 +120,16 @@ public class DTOConverters {
.build();
}
+ /**
+ * Converts a {@link Owner} to a {@link OwnerDTO}.
+ *
+ * @param owner The owner.
+ * @return The owner DTO.
+ */
+ public static OwnerDTO toDTO(Owner owner) {
+ return
OwnerDTO.builder().withName(owner.name()).withType(owner.type()).build();
+ }
+
/**
* Converts a {@link Metalake} to a {@link MetalakeDTO}.
*
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index d387c1672..8a0f19f99 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -403,6 +403,7 @@ public class GravitinoEnv {
this.ownerManager = new OwnerManager(entityStore);
} else {
this.accessControlDispatcher = null;
+ this.ownerManager = null;
}
this.auxServiceManager = new AuxiliaryServiceManager();
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
index 459938ee9..346c39a2c 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
@@ -138,6 +138,11 @@ public class ExceptionHandlers {
.build();
}
+ public static Response handleOwnerException(
+ OperationType type, String name, String metalake, Exception e) {
+ return OwnerExceptionHandler.INSTANCE.handle(type, name, metalake, e);
+ }
+
private static class PartitionExceptionHandler extends BaseExceptionHandler {
private static final ExceptionHandler INSTANCE = new
PartitionExceptionHandler();
@@ -561,6 +566,33 @@ public class ExceptionHandlers {
}
}
+ private static class OwnerExceptionHandler extends BaseExceptionHandler {
+ private static final ExceptionHandler INSTANCE = new
OwnerExceptionHandler();
+
+ private static String getOwnerErrorMsg(
+ String name, String operation, String metalake, String reason) {
+ return String.format(
+ "Failed to execute %s owner operation [%s] under metalake [%s],
reason [%s]",
+ name, operation, metalake, reason);
+ }
+
+ @Override
+ public Response handle(OperationType op, String name, String parent,
Exception e) {
+ String formatted = StringUtil.isBlank(name) ? "" : " [" + name + "]";
+ String errorMsg = getOwnerErrorMsg(formatted, op.name(), parent,
getErrorMsg(e));
+ LOG.warn(errorMsg, e);
+
+ if (e instanceof IllegalArgumentException) {
+ return Utils.illegalArguments(errorMsg, e);
+
+ } else if (e instanceof NotFoundException) {
+ return Utils.notFound(errorMsg, e);
+ } else {
+ return super.handle(op, name, parent, e);
+ }
+ }
+ }
+
@VisibleForTesting
static class BaseExceptionHandler extends ExceptionHandler {
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/OperationType.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/OperationType.java
index 9e611f6e2..37032124f 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/OperationType.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/OperationType.java
@@ -32,4 +32,5 @@ public enum OperationType {
GRANT,
REVOKE,
ASSOCIATE,
+ SET,
}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
new file mode 100644
index 000000000..517b08cd7
--- /dev/null
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
@@ -0,0 +1,116 @@
+/*
+ * 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.server.web.rest;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.Timed;
+import java.util.Locale;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.authorization.OwnerManager;
+import org.apache.gravitino.dto.requests.OwnerSetRequest;
+import org.apache.gravitino.dto.responses.OwnerResponse;
+import org.apache.gravitino.dto.responses.SetResponse;
+import org.apache.gravitino.dto.util.DTOConverters;
+import org.apache.gravitino.metrics.MetricNames;
+import org.apache.gravitino.server.authorization.NameBindings;
+import org.apache.gravitino.server.web.Utils;
+
[email protected]
+@Path("/metalakes/{metalake}/owners")
+public class OwnerOperations {
+
+ private final OwnerManager ownerManager;
+
+ @Context private HttpServletRequest httpRequest;
+
+ public OwnerOperations() {
+ // Because ownerManager may be null when Gravitino doesn't enable
authorization,
+ // and Jersey injection doesn't support null value. So OwnerOperations
chooses to retrieve
+ // ownerManager from GravitinoEnv instead of injection here.
+ this.ownerManager = GravitinoEnv.getInstance().ownerManager();
+ }
+
+ @GET
+ @Path("{metadataObjectType}/{fullName}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "get-object-owner." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "get-object-owner", absolute = true)
+ public Response getOwnerForObject(
+ @PathParam("metalake") String metalake,
+ @PathParam("metadataObjectType") String metadataObjectType,
+ @PathParam("fullName") String fullName) {
+ try {
+ MetadataObject object =
+ MetadataObjects.parse(
+ fullName,
MetadataObject.Type.valueOf(metadataObjectType.toUpperCase(Locale.ROOT)));
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ Optional<Owner> owner = ownerManager.getOwner(metalake, object);
+ if (owner.isPresent()) {
+ return Utils.ok(new
OwnerResponse(DTOConverters.toDTO(owner.get())));
+ } else {
+ return Utils.ok(new OwnerResponse(null));
+ }
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handleOwnerException(
+ OperationType.GET, String.format("metadata object %s", fullName),
metalake, e);
+ }
+ }
+
+ @PUT
+ @Path("{metadataObjectType}/{fullName}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "set-object-owner." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "set-object-owner", absolute = true)
+ public Response setOwnerForObject(
+ @PathParam("metalake") String metalake,
+ @PathParam("metadataObjectType") String metadataObjectType,
+ @PathParam("fullName") String fullName,
+ OwnerSetRequest request) {
+ try {
+ MetadataObject object =
+ MetadataObjects.parse(
+ fullName,
MetadataObject.Type.valueOf(metadataObjectType.toUpperCase(Locale.ROOT)));
+
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ ownerManager.setOwner(metalake, object, request.getName(),
request.getType());
+ return Utils.ok(new SetResponse(true));
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handleOwnerException(
+ OperationType.SET, String.format("metadata object %s", fullName),
metalake, e);
+ }
+ }
+}
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
new file mode 100644
index 000000000..2b0b3ff4a
--- /dev/null
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
@@ -0,0 +1,220 @@
+/*
+ * 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.server.web.rest;
+
+import static org.apache.gravitino.Configs.TREE_LOCK_CLEAN_INTERVAL;
+import static org.apache.gravitino.Configs.TREE_LOCK_MAX_NODE_IN_MEMORY;
+import static org.apache.gravitino.Configs.TREE_LOCK_MIN_NODE_IN_MEMORY;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.authorization.OwnerManager;
+import org.apache.gravitino.dto.authorization.OwnerDTO;
+import org.apache.gravitino.dto.requests.OwnerSetRequest;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.OwnerResponse;
+import org.apache.gravitino.dto.responses.SetResponse;
+import org.apache.gravitino.exceptions.NotFoundException;
+import org.apache.gravitino.lock.LockManager;
+import org.apache.gravitino.rest.RESTUtils;
+import org.glassfish.hk2.utilities.binding.AbstractBinder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class TestOwnerOperations extends JerseyTest {
+ private static final OwnerManager manager = mock(OwnerManager.class);
+
+ private static class MockServletRequestFactory extends
ServletRequestFactoryBase {
+ @Override
+ public HttpServletRequest get() {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getRemoteUser()).thenReturn(null);
+ return request;
+ }
+ }
+
+ @BeforeAll
+ public static void setup() throws IllegalAccessException {
+ Config config = mock(Config.class);
+ Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
+ Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
+ Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL);
+ FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new
LockManager(config), true);
+ FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerManager", manager,
true);
+ }
+
+ @Override
+ protected Application configure() {
+ try {
+ forceSet(
+ TestProperties.CONTAINER_PORT,
String.valueOf(RESTUtils.findAvailablePort(2000, 3000)));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ ResourceConfig resourceConfig = new ResourceConfig();
+ resourceConfig.register(OwnerOperations.class);
+ resourceConfig.register(
+ new AbstractBinder() {
+ @Override
+ protected void configure() {
+
bindFactory(MockServletRequestFactory.class).to(HttpServletRequest.class);
+ }
+ });
+
+ return resourceConfig;
+ }
+
+ @Test
+ void testGetOwnerForObject() {
+ Owner owner =
+ new Owner() {
+ @Override
+ public String name() {
+ return "test";
+ }
+
+ @Override
+ public Type type() {
+ return Type.USER;
+ }
+ };
+
+ when(manager.getOwner(any(), any())).thenReturn(Optional.of(owner));
+
+ Response resp =
+ target("/metalakes/metalake1/owners/metalake/metalake1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+
+ OwnerResponse ownerResponse = resp.readEntity(OwnerResponse.class);
+ Assertions.assertEquals(0, ownerResponse.getCode());
+ OwnerDTO ownerDTO = ownerResponse.getOwner();
+ Assertions.assertEquals("test", ownerDTO.name());
+ Assertions.assertEquals(Owner.Type.USER, ownerDTO.type());
+
+ // Owner is not set
+ when(manager.getOwner(any(), any())).thenReturn(Optional.empty());
+ Response resp1 =
+ target("/metalakes/metalake1/owners/metalake/metalake1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp1.getStatus());
+
+ OwnerResponse ownerResponse1 = resp1.readEntity(OwnerResponse.class);
+ Assertions.assertEquals(0, ownerResponse1.getCode());
+ Assertions.assertNull(ownerResponse1.getOwner());
+
+ // Test to throw NotFoundException
+ doThrow(new NotFoundException("mock error")).when(manager).getOwner(any(),
any());
+ Response resp2 =
+ target("/metalakes/metalake1/owners/metalake/metalake1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp2.getStatus());
+
+ ErrorResponse errorResponse1 = resp2.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResponse1.getCode());
+ Assertions.assertEquals(NotFoundException.class.getSimpleName(),
errorResponse1.getType());
+
+ // Test to throw internal RuntimeException
+ doThrow(new RuntimeException("mock error")).when(manager).getOwner(any(),
any());
+ Response resp3 =
+ target("/metalakes/metalake1/owners/metalake/metalake1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
+
+ ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResponse2.getCode());
+ }
+
+ @Test
+ void testSetOwnerForObject() {
+ OwnerSetRequest request = new OwnerSetRequest("test", Owner.Type.USER);
+ Response resp =
+ target("/metalakes/metalake1/owners/metalake/metalake1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+
+ SetResponse setResponse = resp.readEntity(SetResponse.class);
+ Assertions.assertEquals(0, setResponse.getCode());
+ Assertions.assertTrue(setResponse.set());
+
+ // Test to throw NotFoundException
+ doThrow(new NotFoundException("mock error")).when(manager).setOwner(any(),
any(), any(), any());
+ Response resp2 =
+ target("/metalakes/metalake1/owners/metalake/metalake1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp2.getStatus());
+
+ ErrorResponse errorResponse1 = resp2.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResponse1.getCode());
+ Assertions.assertEquals(NotFoundException.class.getSimpleName(),
errorResponse1.getType());
+
+ // Test to throw internal RuntimeException
+ doThrow(new RuntimeException("mock error")).when(manager).setOwner(any(),
any(), any(), any());
+ Response resp3 =
+ target("/metalakes/metalake1/owners/metalake/metalake1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
+
+ ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResponse2.getCode());
+ }
+}