This is an automated email from the ASF dual-hosted git repository.
yuqi4733 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 545b22ded5 [#7145] feat(policy): implement the policy management on
server-side (part-1) (#7821)
545b22ded5 is described below
commit 545b22ded5e1ebb92f9580db86b679ec6f3d7c86
Author: mchades <[email protected]>
AuthorDate: Fri Aug 8 10:07:09 2025 +0800
[#7145] feat(policy): implement the policy management on server-side
(part-1) (#7821)
### What changes were proposed in this pull request?
implement the policy management on server-side
### Why are the changes needed?
Fix: #7145
### Does this PR introduce _any_ user-facing change?
yes, RESTFul APIs added
### How was this patch tested?
tests addded
---
.../org/apache/gravitino/policy/PolicyChange.java | 26 +-
.../gravitino/dto/policy/PolicyContentDTO.java | 67 +++
.../org/apache/gravitino/dto/policy/PolicyDTO.java | 298 ++++++++++
.../dto/requests/PolicyCreateRequest.java | 163 ++++++
.../gravitino/dto/requests/PolicySetRequest.java | 59 ++
.../dto/requests/PolicyUpdateRequest.java | 174 ++++++
.../dto/requests/PolicyUpdatesRequest.java | 57 ++
.../dto/responses/PolicyListResponse.java | 70 +++
.../gravitino/dto/responses/PolicyResponse.java | 61 +++
.../apache/gravitino/dto/util/DTOConverters.java | 77 +++
.../apache/gravitino/dto/policy/TestPolicyDTO.java | 118 ++++
.../dto/requests/TestPolicyCreateRequest.java | 62 +++
.../dto/requests/TestPolicyUpdatesRequest.java | 94 ++++
.../org/apache/gravitino/policy/PolicyManager.java | 5 +
.../apache/gravitino/policy/TestPolicyManager.java | 11 +-
.../apache/gravitino/server/GravitinoServer.java | 2 +
.../server/web/rest/ExceptionHandlers.java | 45 ++
.../server/web/rest/FilesetOperations.java | 2 +-
.../server/web/rest/PolicyOperations.java | 267 +++++++++
.../gravitino/server/web/rest/TableOperations.java | 2 +-
.../gravitino/server/web/rest/TagOperations.java | 2 +-
.../gravitino/server/web/rest/TopicOperations.java | 2 +-
.../server/web/rest/TestPolicyOperations.java | 597 +++++++++++++++++++++
23 files changed, 2249 insertions(+), 12 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
b/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
index 297572b076..433f441b62 100644
--- a/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
@@ -51,11 +51,12 @@ public interface PolicyChange {
/**
* Creates a new policy change to update the content of the policy.
*
+ * @param policyType The type of the policy, used for validation.
* @param content The new content for the policy.
* @return The policy change.
*/
- static PolicyChange updateContent(PolicyContent content) {
- return new UpdateContent(content);
+ static PolicyChange updateContent(String policyType, PolicyContent content) {
+ return new UpdateContent(policyType, content);
}
/** A policy change to rename the policy. */
@@ -160,12 +161,23 @@ public interface PolicyChange {
/** A policy change to update the content of the policy. */
final class UpdateContent implements PolicyChange {
+ private final String policyType;
private final PolicyContent content;
- private UpdateContent(PolicyContent content) {
+ private UpdateContent(String policyType, PolicyContent content) {
+ this.policyType = policyType;
this.content = content;
}
+ /**
+ * Get the type of the policy.
+ *
+ * @return Get the type of the policy.
+ */
+ public String getPolicyType() {
+ return policyType;
+ }
+
/**
* Get the content of the policy change.
*
@@ -177,19 +189,19 @@ public interface PolicyChange {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (!(o instanceof UpdateContent)) return false;
UpdateContent that = (UpdateContent) o;
- return Objects.equals(content, that.content);
+ return Objects.equals(policyType, that.policyType) &&
Objects.equals(content, that.content);
}
@Override
public int hashCode() {
- return Objects.hash(content);
+ return Objects.hash(policyType, content);
}
@Override
public String toString() {
- return "UPDATE CONTENT " + content;
+ return "UPDATE POLICY CONTENT " + "policyType=" + policyType + ",
content=" + content;
}
}
}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
new file mode 100644
index 0000000000..505881430a
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.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.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.apache.gravitino.policy.PolicyContent;
+
+/** Represents a Policy Content Data Transfer Object (DTO). */
+public interface PolicyContentDTO extends PolicyContent {
+
+ /** Represents a custom policy content DTO. */
+ @EqualsAndHashCode
+ @ToString
+ @Builder(setterPrefix = "with")
+ @AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+ class CustomContentDTO implements PolicyContentDTO {
+
+ @JsonProperty("customRules")
+ private Map<String, Object> customRules;
+
+ @JsonProperty("properties")
+ private Map<String, String> properties;
+
+ // Default constructor for Jackson deserialization only.
+ private CustomContentDTO() {}
+
+ /**
+ * Returns the custom rules defined in this policy content.
+ *
+ * @return a map of custom rules where the key is the rule name and the
value is the rule value.
+ */
+ public Map<String, Object> customRules() {
+ return customRules;
+ }
+
+ @Override
+ public Map<String, String> properties() {
+ return properties;
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ // no validation needed for custom content
+ }
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
new file mode 100644
index 0000000000..520950d681
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
@@ -0,0 +1,298 @@
+/*
+ * 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.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import lombok.ToString;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.dto.AuditDTO;
+import org.apache.gravitino.policy.Policy;
+
+/** Represents a Policy Data Transfer Object (DTO). */
+@ToString
+public class PolicyDTO implements Policy {
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("comment")
+ private String comment;
+
+ @JsonProperty("policyType")
+ private String policyType;
+
+ @JsonProperty("enabled")
+ private boolean enabled;
+
+ @JsonProperty("exclusive")
+ private boolean exclusive;
+
+ @JsonProperty("inheritable")
+ private boolean inheritable;
+
+ @JsonProperty("supportedObjectTypes")
+ private Set<MetadataObject.Type> supportedObjectTypes =
Collections.emptySet();
+
+ @JsonProperty("content")
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+ property = "policyType",
+ defaultImpl = PolicyContentDTO.CustomContentDTO.class)
+ @JsonSubTypes({
+ // add mappings for built-in types here
+ // For example: @JsonSubTypes.Type(value = DataCompactionContent.class,
name =
+ // "system_data_compaction")
+ })
+ private PolicyContentDTO content;
+
+ @JsonProperty("inherited")
+ private Optional<Boolean> inherited = Optional.empty();
+
+ @JsonProperty("audit")
+ private AuditDTO audit;
+
+ private PolicyDTO() {}
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PolicyDTO)) return false;
+ PolicyDTO policyDTO = (PolicyDTO) o;
+ return enabled == policyDTO.enabled
+ && exclusive == policyDTO.exclusive
+ && inheritable == policyDTO.inheritable
+ && Objects.equals(name, policyDTO.name)
+ && Objects.equals(comment, policyDTO.comment)
+ && Objects.equals(policyType, policyDTO.policyType)
+ && Objects.equals(supportedObjectTypes, policyDTO.supportedObjectTypes)
+ && Objects.equals(content, policyDTO.content)
+ && Objects.equals(audit, policyDTO.audit);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ name,
+ comment,
+ policyType,
+ enabled,
+ exclusive,
+ inheritable,
+ supportedObjectTypes,
+ content,
+ audit);
+ }
+
+ /** @return a new builder for constructing a PolicyDTO. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String policyType() {
+ return policyType;
+ }
+
+ @Override
+ public String comment() {
+ return comment;
+ }
+
+ @Override
+ public boolean enabled() {
+ return enabled;
+ }
+
+ @Override
+ public boolean exclusive() {
+ return exclusive;
+ }
+
+ @Override
+ public boolean inheritable() {
+ return inheritable;
+ }
+
+ @Override
+ public Set<MetadataObject.Type> supportedObjectTypes() {
+ return supportedObjectTypes;
+ }
+
+ @Override
+ public PolicyContentDTO content() {
+ return content;
+ }
+
+ @Override
+ public Optional<Boolean> inherited() {
+ return inherited;
+ }
+
+ @Override
+ public AuditDTO auditInfo() {
+ return audit;
+ }
+
+ /** Builder class for constructing PolicyDTO instances. */
+ public static class Builder {
+ private final PolicyDTO policyDTO;
+
+ private Builder() {
+ policyDTO = new PolicyDTO();
+ }
+
+ /**
+ * Sets the name of the policy.
+ *
+ * @param name The name of the policy.
+ * @return The builder instance.
+ */
+ public Builder withName(String name) {
+ policyDTO.name = name;
+ return this;
+ }
+
+ /**
+ * Sets the comment associated with the policy.
+ *
+ * @param comment The comment associated with the policy.
+ * @return The builder instance.
+ */
+ public Builder withComment(String comment) {
+ policyDTO.comment = comment;
+ return this;
+ }
+
+ /**
+ * Sets the type of the policy.
+ *
+ * @param policyType The type of the policy.
+ * @return The builder instance.
+ */
+ public Builder withPolicyType(String policyType) {
+ policyDTO.policyType = policyType;
+ return this;
+ }
+
+ /**
+ * Sets whether the policy is enabled or not.
+ *
+ * @param enabled Whether the policy is enabled.
+ * @return The builder instance.
+ */
+ public Builder withEnabled(boolean enabled) {
+ policyDTO.enabled = enabled;
+ return this;
+ }
+
+ /**
+ * Sets whether the policy is exclusive or not.
+ *
+ * @param exclusive Whether the policy is exclusive.
+ * @return The builder instance.
+ */
+ public Builder withExclusive(boolean exclusive) {
+ policyDTO.exclusive = exclusive;
+ return this;
+ }
+
+ /**
+ * Sets whether the policy is inheritable or not.
+ *
+ * @param inheritable Whether the policy is inheritable.
+ * @return The builder instance.
+ */
+ public Builder withInheritable(boolean inheritable) {
+ policyDTO.inheritable = inheritable;
+ return this;
+ }
+
+ /**
+ * Sets the set of supported metadata object types for the policy.
+ *
+ * @param supportedObjectTypes The set of supported metadata object types.
+ * @return The builder instance.
+ */
+ public Builder withSupportedObjectTypes(Set<MetadataObject.Type>
supportedObjectTypes) {
+ policyDTO.supportedObjectTypes = supportedObjectTypes;
+ return this;
+ }
+
+ /**
+ * Sets the content of the policy.
+ *
+ * @param content The content of the policy.
+ * @return The builder instance.
+ */
+ public Builder withContent(PolicyContentDTO content) {
+ policyDTO.content = content;
+ return this;
+ }
+
+ /**
+ * Sets the audit information for the policy.
+ *
+ * @param audit The audit information for the policy.
+ * @return The builder instance.
+ */
+ public Builder withAudit(AuditDTO audit) {
+ policyDTO.audit = audit;
+ return this;
+ }
+
+ /**
+ * Sets whether the policy is inherited.
+ *
+ * @param inherited Whether the policy is inherited.
+ * @return The builder instance.
+ */
+ public Builder withInherited(Optional<Boolean> inherited) {
+ policyDTO.inherited = inherited;
+ return this;
+ }
+
+ /** @return The constructed Policy DTO. */
+ public PolicyDTO build() {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(policyDTO.name), "policy name cannot be
empty");
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(policyDTO.policyType), "policy type cannot be
empty");
+ Preconditions.checkArgument(policyDTO.content != null, "policy content
cannot be null");
+ Preconditions.checkArgument(
+ CollectionUtils.isNotEmpty(policyDTO.supportedObjectTypes),
+ "supported objectTypes cannot be empty");
+ policyDTO.content.validate();
+ return policyDTO;
+ }
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
new file mode 100644
index 0000000000..9b57050326
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import java.util.Set;
+import javax.annotation.Nullable;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to create a policy. */
+@Getter
+@EqualsAndHashCode
+@ToString
+public class PolicyCreateRequest implements RESTRequest {
+
+ @JsonProperty("name")
+ private final String name;
+
+ @JsonProperty("comment")
+ @Nullable
+ private final String comment;
+
+ @JsonProperty("policyType")
+ private final String policyType;
+
+ @JsonProperty(value = "enabled", defaultValue = "true")
+ private final Boolean enabled;
+
+ @JsonProperty("content")
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+ property = "policyType",
+ defaultImpl = PolicyContentDTO.CustomContentDTO.class)
+ @JsonSubTypes({
+ // add mappings for built-in types here
+ // For example: @JsonSubTypes.Type(value = DataCompactionContent.class,
name =
+ // "system_data_compaction")
+ })
+ private final PolicyContentDTO policyContent;
+
+ @JsonProperty("exclusive")
+ private final Boolean exclusive;
+
+ @JsonProperty("inheritable")
+ private final Boolean inheritable;
+
+ @JsonProperty("supportedObjectTypes")
+ private final Set<MetadataObject.Type> supportedObjectTypes;
+
+ /**
+ * Creates a new PolicyCreateRequest.
+ *
+ * @param name The name of the policy.
+ * @param comment The comment of the policy.
+ * @param type The type of the policy.
+ * @param enabled Whether the policy is enabled.
+ * @param exclusive Whether the policy is exclusive.
+ * @param inheritable Whether the policy is inheritable.
+ * @param supportedObjectTypes The set of metadata object types that the
policy can be applied to.
+ * @param content The content of the policy.
+ */
+ public PolicyCreateRequest(
+ String name,
+ String type,
+ String comment,
+ boolean enabled,
+ boolean exclusive,
+ boolean inheritable,
+ Set<MetadataObject.Type> supportedObjectTypes,
+ PolicyContentDTO content) {
+ this.name = name;
+ this.policyType = type;
+ this.comment = comment;
+ this.enabled = enabled;
+ this.exclusive = exclusive;
+ this.inheritable = inheritable;
+ this.supportedObjectTypes = supportedObjectTypes;
+ this.policyContent = content;
+ }
+
+ /** This is the constructor that is used by Jackson deserializer */
+ private PolicyCreateRequest() {
+ this(null, null, null, true, false, false, null, null);
+ }
+
+ /**
+ * Validates the request.
+ *
+ * @throws IllegalArgumentException If the request is invalid, this
exception is thrown.
+ */
+ @Override
+ public void validate() throws IllegalArgumentException {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(name), "\"name\" is required and cannot be
empty");
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(policyType), "\"policyType\" is required and
cannot be empty");
+
+ Policy.BuiltInType builtInType =
Policy.BuiltInType.fromPolicyType(policyType);
+ if (builtInType != Policy.BuiltInType.CUSTOM) {
+ if (exclusive != null) {
+ Preconditions.checkArgument(
+ exclusive.equals(builtInType.exclusive()),
+ String.format(
+ "Exclusive flag for built-in policy type '%s' must be %s",
+ builtInType, builtInType.exclusive()));
+ }
+
+ if (inheritable != null) {
+ Preconditions.checkArgument(
+ inheritable.equals(builtInType.inheritable()),
+ String.format(
+ "Inheritable flag for built-in policy type '%s' must be %s",
+ builtInType, builtInType.inheritable()));
+ }
+
+ if (supportedObjectTypes != null) {
+ Preconditions.checkArgument(
+ builtInType.supportedObjectTypes().equals(supportedObjectTypes),
+ String.format(
+ "Supported object types for built-in policy type '%s' must be
%s",
+ builtInType, builtInType.supportedObjectTypes()));
+ }
+ } else {
+ Preconditions.checkArgument(
+ exclusive != null, "\"exclusive\" is required for custom policy
type");
+ Preconditions.checkArgument(
+ inheritable != null, "\"inheritable\" is required for custom policy
type");
+ Preconditions.checkArgument(
+ supportedObjectTypes != null,
+ "\"supportedObjectTypes\" is required for custom policy type");
+ }
+ Preconditions.checkArgument(
+ policyContent != null, "\"content\" is required and cannot be null");
+ policyContent.validate();
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicySetRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicySetRequest.java
new file mode 100644
index 0000000000..bb2c05c02a
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicySetRequest.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 policy in use. */
+@Getter
+@EqualsAndHashCode
+@ToString
+public class PolicySetRequest implements RESTRequest {
+
+ @JsonProperty("enable")
+ private final boolean enable;
+
+ /** Default constructor for PolicySetRequest. */
+ public PolicySetRequest() {
+ this(false);
+ }
+
+ /**
+ * Constructor for PolicySetRequest.
+ *
+ * @param enable The enable status to set.
+ */
+ public PolicySetRequest(boolean enable) {
+ this.enable = enable;
+ }
+
+ /**
+ * 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/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
new file mode 100644
index 0000000000..f33236e84f
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
@@ -0,0 +1,174 @@
+/*
+ * 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 static org.apache.gravitino.dto.util.DTOConverters.fromDTO;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.policy.PolicyChange;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to update a policy. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = PolicyUpdateRequest.RenamePolicyRequest.class,
name = "rename"),
+ @JsonSubTypes.Type(
+ value = PolicyUpdateRequest.UpdatePolicyCommentRequest.class,
+ name = "updateComment"),
+ @JsonSubTypes.Type(
+ value = PolicyUpdateRequest.UpdatePolicyContentRequest.class,
+ name = "updateContent")
+})
+public interface PolicyUpdateRequest extends RESTRequest {
+
+ /**
+ * Returns the policy change.
+ *
+ * @return the policy change.
+ */
+ PolicyChange policyChange();
+
+ /** The policy update request for renaming a policy. */
+ @EqualsAndHashCode
+ @Getter
+ @ToString
+ class RenamePolicyRequest implements PolicyUpdateRequest {
+
+ @JsonProperty("newName")
+ private final String newName;
+
+ /**
+ * Creates a new RenamePolicyRequest.
+ *
+ * @param newName The new name of the policy.
+ */
+ public RenamePolicyRequest(String newName) {
+ this.newName = newName;
+ }
+
+ /** This is the constructor that is used by Jackson deserializer */
+ private RenamePolicyRequest() {
+ this(null);
+ }
+
+ @Override
+ public PolicyChange policyChange() {
+ return PolicyChange.rename(newName);
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ Preconditions.checkArgument(StringUtils.isNotBlank(newName),
"\"newName\" must not be blank");
+ }
+ }
+
+ /** The policy update request for updating a policy comment. */
+ @EqualsAndHashCode
+ @ToString
+ @Getter
+ class UpdatePolicyCommentRequest implements PolicyUpdateRequest {
+
+ @JsonProperty("newComment")
+ private final String newComment;
+
+ /**
+ * Creates a new UpdatePolicyCommentRequest.
+ *
+ * @param newComment The new comment of the policy.
+ */
+ public UpdatePolicyCommentRequest(String newComment) {
+ this.newComment = newComment;
+ }
+
+ /** This is the constructor that is used by Jackson deserializer */
+ private UpdatePolicyCommentRequest() {
+ this(null);
+ }
+
+ @Override
+ public PolicyChange policyChange() {
+ return PolicyChange.updateComment(newComment);
+ }
+
+ /** Validates the fields of the request. Always pass. */
+ @Override
+ public void validate() throws IllegalArgumentException {}
+ }
+
+ /** The policy update request for updating a policy content. */
+ @EqualsAndHashCode
+ @Getter
+ @ToString
+ class UpdatePolicyContentRequest implements PolicyUpdateRequest {
+
+ @JsonProperty("policyType")
+ private final String policyType;
+
+ @JsonProperty("newContent")
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
+ property = "policyType",
+ defaultImpl = PolicyContentDTO.CustomContentDTO.class)
+ @JsonSubTypes({
+ // add mappings for built-in types here
+ // For example: @JsonSubTypes.Type(value = DataCompactionContent.class,
name =
+ // "system_data_compaction")
+ })
+ private final PolicyContentDTO newContent;
+
+ /**
+ * Creates a new UpdatePolicyContentRequest.
+ *
+ * @param policyType The type of the policy, used for validation and
serialization. This should
+ * match the type of the content being updated.
+ * @param newContent The new content of the policy.
+ */
+ public UpdatePolicyContentRequest(String policyType, PolicyContentDTO
newContent) {
+ this.policyType = policyType;
+ this.newContent = newContent;
+ }
+
+ /** This is the constructor that is used by Jackson deserializer */
+ private UpdatePolicyContentRequest() {
+ this(null, null);
+ }
+
+ @Override
+ public PolicyChange policyChange() {
+ return PolicyChange.updateContent(policyType, fromDTO(newContent));
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ Preconditions.checkArgument(newContent != null, "\"newContent\" must not
be null");
+ newContent.validate();
+ }
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdatesRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdatesRequest.java
new file mode 100644
index 0000000000..4b529895ec
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdatesRequest.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.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import java.util.List;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to update a policy. */
+@Getter
+@EqualsAndHashCode
+@ToString
+public class PolicyUpdatesRequest implements RESTRequest {
+
+ @JsonProperty("updates")
+ private final List<PolicyUpdateRequest> updates;
+
+ /**
+ * Creates a new PolicyUpdatesRequest.
+ *
+ * @param updates The updates to apply to the policy.
+ */
+ public PolicyUpdatesRequest(List<PolicyUpdateRequest> updates) {
+ this.updates = updates;
+ }
+
+ /** This is the constructor that is used by Jackson deserializer */
+ public PolicyUpdatesRequest() {
+ this(null);
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ Preconditions.checkArgument(updates != null, "updates must not be null");
+ updates.forEach(RESTRequest::validate);
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/responses/PolicyListResponse.java
b/common/src/main/java/org/apache/gravitino/dto/responses/PolicyListResponse.java
new file mode 100644
index 0000000000..a260171c79
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/responses/PolicyListResponse.java
@@ -0,0 +1,70 @@
+/*
+ * 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 java.util.Arrays;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.dto.policy.PolicyDTO;
+
+/** Represents a response for a list of policies. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class PolicyListResponse extends BaseResponse {
+
+ @JsonProperty("policies")
+ private final PolicyDTO[] policies;
+
+ /**
+ * Creates a new PolicyListResponse.
+ *
+ * @param policies The list of policies.
+ */
+ public PolicyListResponse(PolicyDTO[] policies) {
+ super(0);
+ this.policies = policies;
+ }
+
+ /**
+ * This is the constructor that is used by Jackson deserializer to create an
instance of
+ * PolicyListResponse.
+ */
+ public PolicyListResponse() {
+ super();
+ this.policies = null;
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ super.validate();
+
+ Preconditions.checkArgument(policies != null, "\"policies\" must not be
null");
+ Arrays.stream(policies)
+ .forEach(
+ t -> {
+ Preconditions.checkArgument(t != null, "policy must not be
null");
+ Preconditions.checkArgument(t.content() != null, "policy content
must not be null");
+ t.content().validate();
+ });
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/responses/PolicyResponse.java
b/common/src/main/java/org/apache/gravitino/dto/responses/PolicyResponse.java
new file mode 100644
index 0000000000..92645e5d42
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/responses/PolicyResponse.java
@@ -0,0 +1,61 @@
+/*
+ * 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 lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.dto.policy.PolicyDTO;
+
+/** Represents a response for a policy. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class PolicyResponse extends BaseResponse {
+
+ @JsonProperty("policy")
+ private final PolicyDTO policy;
+
+ /**
+ * Creates a new PolicyResponse.
+ *
+ * @param policy The policy.
+ */
+ public PolicyResponse(PolicyDTO policy) {
+ super(0);
+ this.policy = policy;
+ }
+
+ /**
+ * This is the constructor that is used by Jackson deserializer to create an
instance of
+ * PolicyResponse.
+ */
+ private PolicyResponse() {
+ this(null);
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ super.validate();
+
+ Preconditions.checkArgument(policy != null, "\"policy\" must not be null");
+ }
+}
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 7c12cf29a8..020523bf44 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
@@ -57,6 +57,8 @@ import org.apache.gravitino.dto.job.SparkJobTemplateDTO;
import org.apache.gravitino.dto.messaging.TopicDTO;
import org.apache.gravitino.dto.model.ModelDTO;
import org.apache.gravitino.dto.model.ModelVersionDTO;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.dto.policy.PolicyDTO;
import org.apache.gravitino.dto.rel.ColumnDTO;
import org.apache.gravitino.dto.rel.DistributionDTO;
import org.apache.gravitino.dto.rel.SortOrderDTO;
@@ -92,6 +94,9 @@ import org.apache.gravitino.job.SparkJobTemplate;
import org.apache.gravitino.messaging.Topic;
import org.apache.gravitino.model.Model;
import org.apache.gravitino.model.ModelVersion;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.expressions.Expression;
@@ -531,6 +536,57 @@ public class DTOConverters {
return builder.build();
}
+ /**
+ * Converts a Policy to a PolicyDTO.
+ *
+ * @param policy The policy to be converted.
+ * @param inherited The inherited flag.
+ * @return The policy DTO.
+ */
+ public static PolicyDTO toDTO(Policy policy, Optional<Boolean> inherited) {
+ PolicyDTO.Builder builder =
+ PolicyDTO.builder()
+ .withName(policy.name())
+ .withComment(policy.comment())
+ .withPolicyType(policy.policyType())
+ .withEnabled(policy.enabled())
+ .withExclusive(policy.exclusive())
+ .withInheritable(policy.inheritable())
+ .withSupportedObjectTypes(policy.supportedObjectTypes())
+ .withContent(toDTO(policy.content()))
+ .withInherited(inherited)
+ .withAudit(toDTO(policy.auditInfo()))
+ .withInherited(inherited);
+
+ return builder.build();
+ }
+
+ /**
+ * Converts a PolicyContent to a PolicyContentDTO.
+ *
+ * @param policyContent The policyContent to be converted.
+ * @return The policy content DTO.
+ */
+ public static PolicyContentDTO toDTO(PolicyContent policyContent) {
+ if (policyContent == null) {
+ return null;
+ }
+
+ if (policyContent instanceof PolicyContentDTO) {
+ return (PolicyContentDTO) policyContent;
+ }
+
+ if (policyContent instanceof PolicyContents.CustomContent) {
+ PolicyContents.CustomContent customContent =
(PolicyContents.CustomContent) policyContent;
+ return PolicyContentDTO.CustomContentDTO.builder()
+ .withCustomRules(customContent.customRules())
+ .withProperties(customContent.properties())
+ .build();
+ }
+
+ throw new IllegalArgumentException("Unsupported policy content: " +
policyContent);
+ }
+
/**
* Converts credentials to CredentialDTOs.
*
@@ -1188,4 +1244,25 @@ public class DTOConverters {
"Unsupported job template type: " + jobTemplateDTO.jobType());
}
}
+
+ /**
+ * Converts a PolicyContentDTO to a PolicyContent.
+ *
+ * @param policyContentDTO The policy content DTO to be converted.
+ * @return The policy content.
+ */
+ public static PolicyContent fromDTO(PolicyContentDTO policyContentDTO) {
+ if (policyContentDTO == null) {
+ return null;
+ }
+
+ if (policyContentDTO instanceof PolicyContentDTO.CustomContentDTO) {
+ PolicyContentDTO.CustomContentDTO customContentDTO =
+ (PolicyContentDTO.CustomContentDTO) policyContentDTO;
+ return PolicyContents.custom(customContentDTO.customRules(),
customContentDTO.properties());
+ }
+
+ throw new IllegalArgumentException(
+ "Unsupported policy content type: " + policyContentDTO.getClass());
+ }
}
diff --git
a/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
b/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
new file mode 100644
index 0000000000..ebb173409e
--- /dev/null
+++ b/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
@@ -0,0 +1,118 @@
+/*
+ * 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.policy;
+
+import static org.apache.gravitino.policy.Policy.SUPPORTS_ALL_OBJECT_TYPES;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableMap;
+import java.time.Instant;
+import java.util.Optional;
+import org.apache.gravitino.dto.AuditDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyDTO {
+
+ @Test
+ public void testPolicySerDe() throws JsonProcessingException {
+ AuditDTO audit =
AuditDTO.builder().withCreator("user1").withCreateTime(Instant.now()).build();
+ PolicyContentDTO.CustomContentDTO customContent =
+ PolicyContentDTO.CustomContentDTO.builder()
+ .withCustomRules(ImmutableMap.of("key1", "value1"))
+ .withProperties(ImmutableMap.of("prop1", "value1"))
+ .build();
+
+ PolicyDTO policyDTO =
+ PolicyDTO.builder()
+ .withName("policy_test")
+ .withComment("policy comment")
+ .withPolicyType("my_compaction")
+ .withEnabled(true)
+ .withInheritable(true)
+ .withExclusive(true)
+ .withInheritable(true)
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(customContent)
+ .withAudit(audit)
+ .build();
+
+ String serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO);
+ PolicyDTO deserPolicyDTO = JsonUtils.objectMapper().readValue(serJson,
PolicyDTO.class);
+ Assertions.assertEquals(policyDTO, deserPolicyDTO);
+
+ Assertions.assertEquals("policy_test", deserPolicyDTO.name());
+ Assertions.assertEquals("policy comment", deserPolicyDTO.comment());
+ Assertions.assertEquals("my_compaction", deserPolicyDTO.policyType());
+ Assertions.assertTrue(deserPolicyDTO.enabled());
+ Assertions.assertTrue(deserPolicyDTO.inheritable());
+ Assertions.assertTrue(deserPolicyDTO.exclusive());
+ Assertions.assertEquals(SUPPORTS_ALL_OBJECT_TYPES,
deserPolicyDTO.supportedObjectTypes());
+ Assertions.assertEquals(customContent, deserPolicyDTO.content());
+ Assertions.assertEquals(audit, deserPolicyDTO.auditInfo());
+
+ // Test policy with inherited
+ PolicyDTO policyDTO2 =
+ PolicyDTO.builder()
+ .withName("policy_test")
+ .withComment("policy comment")
+ .withPolicyType("my_compaction")
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(customContent)
+ .withAudit(audit)
+ .withInherited(Optional.empty())
+ .build();
+
+ serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO2);
+ PolicyDTO deserPolicyDTO2 = JsonUtils.objectMapper().readValue(serJson,
PolicyDTO.class);
+ Assertions.assertEquals(policyDTO2, deserPolicyDTO2);
+ Assertions.assertEquals(Optional.empty(), deserPolicyDTO2.inherited());
+
+ PolicyDTO policyDTO3 =
+ PolicyDTO.builder()
+ .withName("policy_test")
+ .withComment("policy comment")
+ .withPolicyType("my_compaction")
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(customContent)
+ .withAudit(audit)
+ .withInherited(Optional.of(false))
+ .build();
+
+ serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO3);
+ PolicyDTO deserPolicyDTO3 = JsonUtils.objectMapper().readValue(serJson,
PolicyDTO.class);
+ Assertions.assertEquals(Optional.of(false), deserPolicyDTO3.inherited());
+
+ PolicyDTO policyDTO4 =
+ PolicyDTO.builder()
+ .withName("policy_test")
+ .withComment("policy comment")
+ .withPolicyType("my_compaction")
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(customContent)
+ .withAudit(audit)
+ .withInherited(Optional.of(true))
+ .build();
+
+ serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO4);
+ PolicyDTO deserPolicyDTO4 = JsonUtils.objectMapper().readValue(serJson,
PolicyDTO.class);
+ Assertions.assertEquals(Optional.of(true), deserPolicyDTO4.inherited());
+ }
+}
diff --git
a/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyCreateRequest.java
b/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyCreateRequest.java
new file mode 100644
index 0000000000..1551719833
--- /dev/null
+++
b/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyCreateRequest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.core.JsonProcessingException;
+import com.google.common.collect.ImmutableMap;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.policy.Policy;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyCreateRequest {
+
+ @Test
+ public void testPolicyCreateRequestSerDe() throws JsonProcessingException {
+ PolicyContentDTO content =
+ PolicyContentDTO.CustomContentDTO.builder()
+ .withCustomRules(ImmutableMap.of("rule1", "value1"))
+ .withProperties(null)
+ .build();
+ PolicyCreateRequest request =
+ new PolicyCreateRequest(
+ "policy_test",
+ "test_type",
+ "policy comment",
+ true,
+ true,
+ true,
+ Policy.SUPPORTS_ALL_OBJECT_TYPES,
+ content);
+ String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+ PolicyCreateRequest deserRequest =
+ JsonUtils.objectMapper().readValue(serJson, PolicyCreateRequest.class);
+ Assertions.assertEquals(request, deserRequest);
+ Assertions.assertEquals("policy_test", deserRequest.getName());
+ Assertions.assertEquals("policy comment", deserRequest.getComment());
+ Assertions.assertEquals("test_type", deserRequest.getPolicyType());
+ Assertions.assertTrue(deserRequest.getEnabled());
+ Assertions.assertTrue(deserRequest.getExclusive());
+ Assertions.assertTrue(deserRequest.getInheritable());
+ Assertions.assertEquals(
+ Policy.SUPPORTS_ALL_OBJECT_TYPES,
deserRequest.getSupportedObjectTypes());
+ Assertions.assertEquals(content, deserRequest.getPolicyContent());
+ }
+}
diff --git
a/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyUpdatesRequest.java
b/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyUpdatesRequest.java
new file mode 100644
index 0000000000..98da580afe
--- /dev/null
+++
b/common/src/test/java/org/apache/gravitino/dto/requests/TestPolicyUpdatesRequest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.core.JsonProcessingException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyUpdatesRequest {
+
+ @Test
+ public void testRenamePolicyRequestSerDe() throws JsonProcessingException {
+ PolicyUpdateRequest.RenamePolicyRequest request =
+ new PolicyUpdateRequest.RenamePolicyRequest("policy_test_new");
+ String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+ PolicyUpdateRequest.RenamePolicyRequest deserRequest =
+ JsonUtils.objectMapper().readValue(serJson,
PolicyUpdateRequest.RenamePolicyRequest.class);
+ Assertions.assertEquals(request, deserRequest);
+ Assertions.assertEquals("policy_test_new", deserRequest.getNewName());
+ }
+
+ @Test
+ public void testUpdatePolicyCommentRequestSerDe() throws
JsonProcessingException {
+ PolicyUpdateRequest.UpdatePolicyCommentRequest request =
+ new PolicyUpdateRequest.UpdatePolicyCommentRequest("policy comment
new");
+ String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+ PolicyUpdateRequest.UpdatePolicyCommentRequest deserRequest =
+ JsonUtils.objectMapper()
+ .readValue(serJson,
PolicyUpdateRequest.UpdatePolicyCommentRequest.class);
+ Assertions.assertEquals(request, deserRequest);
+ Assertions.assertEquals("policy comment new",
deserRequest.getNewComment());
+ }
+
+ @Test
+ public void testUpdatePolicyContentRequestSerDe() throws
JsonProcessingException {
+ PolicyContentDTO contentDTO =
+ PolicyContentDTO.CustomContentDTO.builder()
+ .withCustomRules(ImmutableMap.of("rule1", "value1"))
+ .withProperties(ImmutableMap.of("key", "value"))
+ .build();
+ PolicyUpdateRequest.UpdatePolicyContentRequest request =
+ new PolicyUpdateRequest.UpdatePolicyContentRequest("test_type",
contentDTO);
+ String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+ PolicyUpdateRequest.UpdatePolicyContentRequest deserRequest =
+ JsonUtils.objectMapper()
+ .readValue(serJson,
PolicyUpdateRequest.UpdatePolicyContentRequest.class);
+ Assertions.assertEquals(request, deserRequest);
+ Assertions.assertEquals("test_type", deserRequest.getPolicyType());
+ Assertions.assertEquals(contentDTO, deserRequest.getNewContent());
+ }
+
+ @Test
+ public void testPolicyUpdatesRequestSerDe() throws JsonProcessingException {
+ PolicyUpdateRequest request = new
PolicyUpdateRequest.RenamePolicyRequest("policy_test_new");
+ PolicyUpdateRequest request1 =
+ new PolicyUpdateRequest.UpdatePolicyCommentRequest("policy comment
new");
+ PolicyUpdateRequest request2 =
+ new PolicyUpdateRequest.UpdatePolicyContentRequest(
+ "test_type",
+ PolicyContentDTO.CustomContentDTO.builder()
+ .withCustomRules(ImmutableMap.of("rule1", "value1"))
+ .withProperties(ImmutableMap.of("key", "value"))
+ .build());
+
+ List<PolicyUpdateRequest> updates = ImmutableList.of(request, request1,
request2);
+ PolicyUpdatesRequest policyUpdatesRequest = new
PolicyUpdatesRequest(updates);
+ String serJson =
JsonUtils.objectMapper().writeValueAsString(policyUpdatesRequest);
+ PolicyUpdatesRequest deserRequest =
+ JsonUtils.objectMapper().readValue(serJson,
PolicyUpdatesRequest.class);
+ Assertions.assertEquals(policyUpdatesRequest, deserRequest);
+ Assertions.assertEquals(updates, deserRequest.getUpdates());
+ }
+}
diff --git a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
index 2e7485c5fa..cf67a10aa9 100644
--- a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
+++ b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
@@ -491,6 +491,11 @@ public class PolicyManager implements PolicyDispatcher {
newComment = ((PolicyChange.UpdatePolicyComment)
change).getNewComment();
} else if (change instanceof PolicyChange.UpdateContent) {
PolicyChange.UpdateContent updateContent =
(PolicyChange.UpdateContent) change;
+ Preconditions.checkArgument(
+
policyEntity.policyType().equalsIgnoreCase(updateContent.getPolicyType()),
+ "Policy type mismatch: expected %s but got %s",
+ policyEntity.policyType(),
+ updateContent.getPolicyType());
newContent = updateContent.getContent();
} else {
throw new IllegalArgumentException("Unsupported policy change: " +
change);
diff --git
a/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
b/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
index 7cd95b88fa..dc92e25415 100644
--- a/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
+++ b/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
@@ -334,11 +334,20 @@ public class TestPolicyManager {
// test update content
Map<String, Object> newCustomRules = ImmutableMap.of("rule3", 1, "rule4",
"value2");
PolicyContent newContent = PolicyContents.custom(newCustomRules, null);
- PolicyChange contentChange = PolicyChange.updateContent(newContent);
+ PolicyChange contentChange = PolicyChange.updateContent("test",
newContent);
Policy updatedContentPolicy =
policyManager.alterPolicy(METALAKE, changedCommentPolicy.name(),
contentChange);
Assertions.assertEquals(newContent, updatedContentPolicy.content());
+ // test policy type mismatch
+ PolicyChange typeChange = PolicyChange.updateContent("mismatch_type",
newContent);
+ Exception e =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> policyManager.alterPolicy(METALAKE,
updatedContentPolicy.name(), typeChange));
+ Assertions.assertEquals(
+ "Policy type mismatch: expected test but got mismatch_type",
e.getMessage());
+
// test disable policy
Assertions.assertDoesNotThrow(
() -> policyManager.disablePolicy(METALAKE, renamedPolicy.name()));
diff --git
a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
index da76701444..3063a12278 100644
--- a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
+++ b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
@@ -41,6 +41,7 @@ import org.apache.gravitino.lineage.LineageService;
import org.apache.gravitino.metalake.MetalakeDispatcher;
import org.apache.gravitino.metrics.MetricsSystem;
import org.apache.gravitino.metrics.source.MetricsSource;
+import org.apache.gravitino.policy.PolicyDispatcher;
import org.apache.gravitino.server.authentication.ServerAuthenticator;
import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
import org.apache.gravitino.server.web.ConfigServlet;
@@ -139,6 +140,7 @@ public class GravitinoServer extends ResourceConfig {
bind(gravitinoEnv.filesetDispatcher()).to(FilesetDispatcher.class).ranked(1);
bind(gravitinoEnv.topicDispatcher()).to(TopicDispatcher.class).ranked(1);
bind(gravitinoEnv.tagDispatcher()).to(TagDispatcher.class).ranked(1);
+
bind(gravitinoEnv.policyDispatcher()).to(PolicyDispatcher.class).ranked(1);
bind(gravitinoEnv.credentialOperationDispatcher())
.to(CredentialOperationDispatcher.class)
.ranked(1);
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 4e6190a440..2e933c10c9 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
@@ -42,6 +42,8 @@ import
org.apache.gravitino.exceptions.NonEmptySchemaException;
import org.apache.gravitino.exceptions.NotFoundException;
import org.apache.gravitino.exceptions.NotInUseException;
import org.apache.gravitino.exceptions.PartitionAlreadyExistsException;
+import org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
+import org.apache.gravitino.exceptions.PolicyAlreadyExistsException;
import org.apache.gravitino.exceptions.RoleAlreadyExistsException;
import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
import org.apache.gravitino.exceptions.TableAlreadyExistsException;
@@ -124,6 +126,11 @@ public class ExceptionHandlers {
return TagExceptionHandler.INSTANCE.handle(op, tag, parent, e);
}
+ public static Response handlePolicyException(
+ OperationType op, String policy, String parent, Exception e) {
+ return PolicyExceptionHandler.INSTANCE.handle(op, policy, parent, e);
+ }
+
public static Response handleCredentialException(
OperationType op, String metadataObjectName, Exception e) {
return CredentialExceptionHandler.INSTANCE.handle(op, metadataObjectName,
"", e);
@@ -715,6 +722,44 @@ public class ExceptionHandlers {
}
}
+ private static class PolicyExceptionHandler extends BaseExceptionHandler {
+
+ private static final ExceptionHandler INSTANCE = new
PolicyExceptionHandler();
+
+ private static String getPolicyErrorMsg(
+ String policy, String operation, String parent, String reason) {
+ return String.format(
+ "Failed to operate policy(s)%s operation [%s] under object [%s],
reason [%s]",
+ policy, operation, parent, reason);
+ }
+
+ @Override
+ public Response handle(OperationType op, String policy, String parent,
Exception e) {
+ String formatted = StringUtil.isBlank(policy) ? "" : " [" + policy + "]";
+ String errorMsg = getPolicyErrorMsg(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 if (e instanceof PolicyAlreadyExistsException) {
+ return Utils.alreadyExists(errorMsg, e);
+
+ } else if (e instanceof PolicyAlreadyAssociatedException) {
+ return Utils.alreadyExists(errorMsg, e);
+
+ } else if (e instanceof NotInUseException) {
+ return Utils.notInUse(errorMsg, e);
+
+ } else {
+ return super.handle(op, policy, parent, e);
+ }
+ }
+ }
+
private static class OwnerExceptionHandler extends BaseExceptionHandler {
private static final ExceptionHandler INSTANCE = new
OwnerExceptionHandler();
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java
index 68f8ef35ce..b0165b27e6 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java
@@ -335,7 +335,7 @@ public class FilesetOperations {
NameIdentifier ident = NameIdentifierUtil.ofFileset(metalake,
catalog, schema, fileset);
boolean dropped = dispatcher.dropFileset(ident);
if (!dropped) {
- LOG.warn("Failed to drop fileset {} under schema {}", fileset,
schema);
+ LOG.warn("Cannot find to be dropped fileset {} under schema {}",
fileset, schema);
}
Response response = Utils.ok(new DropResponse(dropped));
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
new file mode 100644
index 0000000000..93ec30eb32
--- /dev/null
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
@@ -0,0 +1,267 @@
+/*
+ * 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.dto.util.DTOConverters.fromDTO;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.Timed;
+import java.util.Arrays;
+import java.util.Optional;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+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;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.policy.PolicyDTO;
+import org.apache.gravitino.dto.requests.PolicyCreateRequest;
+import org.apache.gravitino.dto.requests.PolicySetRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdateRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdatesRequest;
+import org.apache.gravitino.dto.responses.BaseResponse;
+import org.apache.gravitino.dto.responses.DropResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.dto.responses.PolicyListResponse;
+import org.apache.gravitino.dto.responses.PolicyResponse;
+import org.apache.gravitino.dto.util.DTOConverters;
+import org.apache.gravitino.metrics.MetricNames;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyChange;
+import org.apache.gravitino.policy.PolicyDispatcher;
+import org.apache.gravitino.server.web.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path("metalakes/{metalake}/policies")
+public class PolicyOperations {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(PolicyOperations.class);
+
+ private final PolicyDispatcher policyDispatcher;
+
+ @Context private HttpServletRequest httpRequest;
+
+ @Inject
+ public PolicyOperations(PolicyDispatcher policyDispatcher) {
+ this.policyDispatcher = policyDispatcher;
+ }
+
+ @GET
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "list-policies." + MetricNames.HTTP_PROCESS_DURATION, absolute
= true)
+ @ResponseMetered(name = "list-policies", absolute = true)
+ public Response listPolicies(
+ @PathParam("metalake") String metalake,
+ @QueryParam("details") @DefaultValue("false") boolean verbose) {
+ LOG.info(
+ "Received list policy {} request for metalake: {}", verbose ? "infos"
: "names", metalake);
+
+ try {
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ if (verbose) {
+ Policy[] policies = policyDispatcher.listPolicyInfos(metalake);
+ PolicyDTO[] policyDTOs =
+ Arrays.stream(policies)
+ .map(p -> DTOConverters.toDTO(p, Optional.empty()))
+ .toArray(PolicyDTO[]::new);
+
+ LOG.info("List {} policies info under metalake: {}",
policyDTOs.length, metalake);
+ return Utils.ok(new PolicyListResponse(policyDTOs));
+
+ } else {
+ String[] policyNames = policyDispatcher.listPolicies(metalake);
+ policyNames = policyNames == null ? new String[0] : policyNames;
+
+ LOG.info("List {} policies under metalake: {}",
policyNames.length, metalake);
+ return Utils.ok(new NameListResponse(policyNames));
+ }
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handlePolicyException(OperationType.LIST, "",
metalake, e);
+ }
+ }
+
+ @POST
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "create-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute
= true)
+ @ResponseMetered(name = "create-policy", absolute = true)
+ public Response createPolicy(
+ @PathParam("metalake") String metalake, PolicyCreateRequest request) {
+ LOG.info("Received create policy request under metalake: {}", metalake);
+
+ try {
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ request.validate();
+ Policy policy =
+ Policy.BuiltInType.fromPolicyType(request.getPolicyType())
+ == Policy.BuiltInType.CUSTOM
+ ? policyDispatcher.createPolicy(
+ metalake,
+ request.getName(),
+ request.getPolicyType(),
+ request.getComment(),
+ request.getEnabled(),
+ request.getExclusive(),
+ request.getInheritable(),
+ request.getSupportedObjectTypes(),
+ fromDTO(request.getPolicyContent()))
+ : policyDispatcher.createPolicy(
+ metalake,
+ request.getName(),
+ request.getPolicyType(),
+ request.getComment(),
+ request.getEnabled(),
+ fromDTO(request.getPolicyContent()));
+
+ LOG.info("Created policy: {} under metalake: {}", policy.name(),
metalake);
+ return Utils.ok(new PolicyResponse(DTOConverters.toDTO(policy,
Optional.empty())));
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handlePolicyException(
+ OperationType.CREATE, request.getName(), metalake, e);
+ }
+ }
+
+ @GET
+ @Path("{policy}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "get-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
+ @ResponseMetered(name = "get-policy", absolute = true)
+ public Response getPolicy(
+ @PathParam("metalake") String metalake, @PathParam("policy") String
name) {
+ LOG.info("Received get policy request for policy: {} under metalake: {}",
name, metalake);
+
+ try {
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ Policy policy = policyDispatcher.getPolicy(metalake, name);
+ LOG.info("Get policy: {} under metalake: {}", name, metalake);
+ return Utils.ok(new PolicyResponse(DTOConverters.toDTO(policy,
Optional.empty())));
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handlePolicyException(OperationType.GET, name,
metalake, e);
+ }
+ }
+
+ @PUT
+ @Path("{policy}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "alter-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute
= true)
+ @ResponseMetered(name = "alter-policy", absolute = true)
+ public Response alterPolicy(
+ @PathParam("metalake") String metalake,
+ @PathParam("policy") String name,
+ PolicyUpdatesRequest request) {
+ LOG.info("Received alter policy request for policy: {} under metalake:
{}", name, metalake);
+
+ try {
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ request.validate();
+
+ PolicyChange[] changes =
+ request.getUpdates().stream()
+ .map(PolicyUpdateRequest::policyChange)
+ .toArray(PolicyChange[]::new);
+ Policy policy = policyDispatcher.alterPolicy(metalake, name,
changes);
+
+ LOG.info("Altered policy: {} under metalake: {}", name, metalake);
+ return Utils.ok(new PolicyResponse(DTOConverters.toDTO(policy,
Optional.empty())));
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handlePolicyException(OperationType.ALTER,
name, metalake, e);
+ }
+ }
+
+ @PATCH
+ @Path("{policy}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "set-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
+ @ResponseMetered(name = "set-policy", absolute = true)
+ public Response setPolicy(
+ @PathParam("metalake") String metalake,
+ @PathParam("policy") String name,
+ PolicySetRequest request) {
+ LOG.info("Received set policy request for policy: {} under metalake: {}",
name, metalake);
+
+ try {
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ if (request.isEnable()) {
+ policyDispatcher.enablePolicy(metalake, name);
+ } else {
+ policyDispatcher.disablePolicy(metalake, name);
+ }
+
+ Response response = Utils.ok(new BaseResponse());
+ LOG.info(
+ "Successfully {} policy: {} under metalake: {}",
+ request.isEnable() ? "enabled" : "disabled",
+ name,
+ metalake);
+ return response;
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handlePolicyException(OperationType.ENABLE,
name, metalake, e);
+ }
+ }
+
+ @DELETE
+ @Path("{policy}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "delete-policy." + MetricNames.HTTP_PROCESS_DURATION, absolute
= true)
+ @ResponseMetered(name = "delete-policy", absolute = true)
+ public Response deletePolicy(
+ @PathParam("metalake") String metalake, @PathParam("policy") String
name) {
+ LOG.info("Received delete policy request for policy: {} under metalake:
{}", name, metalake);
+
+ try {
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ boolean deleted = policyDispatcher.deletePolicy(metalake, name);
+ if (!deleted) {
+ LOG.warn("Cannot find to be deleted policy {} under metalake
{}", name, metalake);
+ } else {
+ LOG.info("Deleted policy: {} under metalake: {}", name,
metalake);
+ }
+
+ return Utils.ok(new DropResponse(deleted));
+ });
+ } catch (Exception e) {
+ return ExceptionHandlers.handlePolicyException(OperationType.DELETE,
name, metalake, e);
+ }
+ }
+}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
index 3b20fe6d02..4302b737a9 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/TableOperations.java
@@ -261,7 +261,7 @@ public class TableOperations {
NameIdentifier ident = NameIdentifierUtil.ofTable(metalake,
catalog, schema, table);
boolean dropped = purge ? dispatcher.purgeTable(ident) :
dispatcher.dropTable(ident);
if (!dropped) {
- LOG.warn("Failed to drop table {} under schema {}", table,
schema);
+ LOG.warn("Cannot find to be dropped table {} under schema {}",
table, schema);
}
Response response = Utils.ok(new DropResponse(dropped));
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
index 4166a83ebb..1b91b23cfd 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
@@ -204,7 +204,7 @@ public class TagOperations {
() -> {
boolean deleted = tagDispatcher.deleteTag(metalake, name);
if (!deleted) {
- LOG.warn("Failed to delete tag {} under metalake {}", name,
metalake);
+ LOG.warn("Cannot find to be deleted tag {} under metalake {}",
name, metalake);
} else {
LOG.info("Deleted tag: {} under metalake: {}", name, metalake);
}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/TopicOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/TopicOperations.java
index 7712316ece..5e273643e9 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/TopicOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/TopicOperations.java
@@ -250,7 +250,7 @@ public class TopicOperations {
boolean dropped = dispatcher.dropTopic(ident);
if (!dropped) {
- LOG.warn("Failed to drop topic {} under schema {}", topic,
schema);
+ LOG.warn("Cannot find to be dropped topic {} under schema {}",
topic, schema);
}
Response response = Utils.ok(new DropResponse(dropped));
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
new file mode 100644
index 0000000000..917a86afc6
--- /dev/null
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
@@ -0,0 +1,597 @@
+/*
+ * 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.dto.util.DTOConverters.toDTO;
+import static org.apache.gravitino.policy.Policy.SUPPORTS_ALL_OBJECT_TYPES;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.time.Instant;
+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.gravitino.dto.requests.PolicyCreateRequest;
+import org.apache.gravitino.dto.requests.PolicySetRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdateRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdatesRequest;
+import org.apache.gravitino.dto.responses.BaseResponse;
+import org.apache.gravitino.dto.responses.DropResponse;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.dto.responses.PolicyListResponse;
+import org.apache.gravitino.dto.responses.PolicyResponse;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchPolicyException;
+import org.apache.gravitino.exceptions.PolicyAlreadyExistsException;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.PolicyEntity;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyChange;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.policy.PolicyDispatcher;
+import org.apache.gravitino.policy.PolicyManager;
+import org.apache.gravitino.rest.RESTUtils;
+import org.glassfish.jersey.client.HttpUrlConnectorProvider;
+import org.glassfish.jersey.internal.inject.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.Test;
+
+public class TestPolicyOperations extends JerseyTest {
+
+ private static class MockServletRequestFactory extends
ServletRequestFactoryBase {
+
+ @Override
+ public HttpServletRequest get() {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getRemoteUser()).thenReturn(null);
+ return request;
+ }
+ }
+
+ private final PolicyManager policyManager = mock(PolicyManager.class);
+
+ private final String metalake = "test_metalake";
+
+ private final AuditInfo testAuditInfo1 =
+
AuditInfo.builder().withCreator("user1").withCreateTime(Instant.now()).build();
+
+ @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(PolicyOperations.class);
+ resourceConfig.register(
+ new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(policyManager).to(PolicyDispatcher.class).ranked(2);
+ bindFactory(TestPolicyOperations.MockServletRequestFactory.class)
+ .to(HttpServletRequest.class);
+ }
+ });
+
+ return resourceConfig;
+ }
+
+ @Test
+ public void testListPolicies() {
+ String[] policies = new String[] {"policy1", "policy2"};
+ when(policyManager.listPolicies(metalake)).thenReturn(policies);
+
+ Response response =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
response.getMediaType());
+
+ NameListResponse nameListResponse =
response.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse.getCode());
+ Assertions.assertArrayEquals(policies, nameListResponse.getNames());
+
+ when(policyManager.listPolicies(metalake)).thenReturn(null);
+ Response resp1 =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp1.getStatus());
+
+ NameListResponse nameListResponse1 =
resp1.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse1.getCode());
+ Assertions.assertEquals(0, nameListResponse1.getNames().length);
+
+ when(policyManager.listPolicies(metalake)).thenReturn(new String[0]);
+ Response resp2 =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp2.getStatus());
+
+ NameListResponse nameListResponse2 =
resp2.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse2.getCode());
+ Assertions.assertEquals(0, nameListResponse2.getNames().length);
+
+ // Test throw NoSuchMetalakeException
+ doThrow(new NoSuchMetalakeException("mock
error")).when(policyManager).listPolicies(metalake);
+ Response resp3 =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp3.getStatus());
+
+ ErrorResponse errorResp = resp3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResp.getCode());
+ Assertions.assertEquals(NoSuchMetalakeException.class.getSimpleName(),
errorResp.getType());
+
+ // Test throw RuntimeException
+ doThrow(new RuntimeException("mock
error")).when(policyManager).listPolicies(metalake);
+ Response resp4 =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp4.getStatus());
+
+ ErrorResponse errorResp1 = resp4.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResp1.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp1.getType());
+ }
+
+ @Test
+ public void testListPolicyInfos() {
+ ImmutableMap<String, Object> contentFields =
ImmutableMap.of("target_file_size_bytes", 1000);
+ PolicyContent content = PolicyContents.custom(contentFields, null);
+ PolicyEntity policy1 =
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("policy1")
+ .withPolicyType("my_compaction")
+ .withEnabled(false)
+ .withExclusive(true)
+ .withInheritable(true)
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(content)
+ .withAuditInfo(testAuditInfo1)
+ .build();
+
+ PolicyEntity policy2 =
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("policy2")
+ .withPolicyType("my_compaction")
+ .withEnabled(false)
+ .withExclusive(true)
+ .withInheritable(true)
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(content)
+ .withAuditInfo(testAuditInfo1)
+ .build();
+
+ Policy[] policies = new Policy[] {policy1, policy2};
+ when(policyManager.listPolicyInfos(metalake)).thenReturn(policies);
+
+ Response resp =
+ target(policyPath(metalake))
+ .queryParam("details", true)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ PolicyListResponse policyListResp =
resp.readEntity(PolicyListResponse.class);
+ Assertions.assertEquals(0, policyListResp.getCode());
+ Assertions.assertEquals(policies.length,
policyListResp.getPolicies().length);
+
+ Assertions.assertEquals(policy1.name(),
policyListResp.getPolicies()[0].name());
+ Assertions.assertEquals(policy1.comment(),
policyListResp.getPolicies()[0].comment());
+ Assertions.assertEquals(Optional.empty(),
policyListResp.getPolicies()[0].inherited());
+
+ Assertions.assertEquals(policy2.name(),
policyListResp.getPolicies()[1].name());
+ Assertions.assertEquals(policy2.comment(),
policyListResp.getPolicies()[1].comment());
+ Assertions.assertEquals(Optional.empty(),
policyListResp.getPolicies()[1].inherited());
+
+ // Test return empty array
+ when(policyManager.listPolicyInfos(metalake)).thenReturn(new Policy[0]);
+ Response resp2 =
+ target(policyPath(metalake))
+ .queryParam("details", true)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp2.getStatus());
+
+ PolicyListResponse policyListResp2 =
resp2.readEntity(PolicyListResponse.class);
+ Assertions.assertEquals(0, policyListResp2.getCode());
+ Assertions.assertEquals(0, policyListResp2.getPolicies().length);
+ }
+
+ @Test
+ public void testCreatePolicy() {
+ ImmutableMap<String, Object> contentFields =
ImmutableMap.of("target_file_size_bytes", 1000);
+ PolicyContent content = PolicyContents.custom(contentFields, null);
+ PolicyEntity policy1 =
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("policy1")
+ .withPolicyType("my_compaction")
+ .withEnabled(false)
+ .withExclusive(true)
+ .withInheritable(true)
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(content)
+ .withAuditInfo(testAuditInfo1)
+ .build();
+ when(policyManager.createPolicy(
+ metalake,
+ "policy1",
+ "my_compaction",
+ null,
+ false,
+ true,
+ true,
+ SUPPORTS_ALL_OBJECT_TYPES,
+ content))
+ .thenReturn(policy1);
+
+ PolicyCreateRequest request =
+ new PolicyCreateRequest(
+ "policy1",
+ "my_compaction",
+ null,
+ false,
+ true,
+ true,
+ SUPPORTS_ALL_OBJECT_TYPES,
+ toDTO(content));
+ Response resp =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ PolicyResponse policyResp = resp.readEntity(PolicyResponse.class);
+ Assertions.assertEquals(0, policyResp.getCode());
+
+ Policy respPolicy = policyResp.getPolicy();
+ Assertions.assertEquals(policy1.name(), respPolicy.name());
+ Assertions.assertEquals(policy1.comment(), respPolicy.comment());
+ Assertions.assertEquals(Optional.empty(), respPolicy.inherited());
+
+ // Test throw PolicyAlreadyExistsException
+ doThrow(new PolicyAlreadyExistsException("mock error"))
+ .when(policyManager)
+ .createPolicy(
+ any(), any(), any(), any(), anyBoolean(), anyBoolean(),
anyBoolean(), any(), any());
+ Response resp1 =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
resp1.getStatus());
+
+ ErrorResponse errorResp = resp1.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.ALREADY_EXISTS_CODE,
errorResp.getCode());
+ Assertions.assertEquals(
+ PolicyAlreadyExistsException.class.getSimpleName(),
errorResp.getType());
+
+ // Test throw RuntimeException
+ doThrow(new RuntimeException("mock error"))
+ .when(policyManager)
+ .createPolicy(
+ any(), any(), any(), any(), anyBoolean(), anyBoolean(),
anyBoolean(), any(), any());
+
+ Response resp2 =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp2.getStatus());
+
+ ErrorResponse errorResp1 = resp2.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResp1.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp1.getType());
+ }
+
+ @Test
+ public void testGetPolicy() {
+ ImmutableMap<String, Object> contentFields =
ImmutableMap.of("target_file_size_bytes", 1000);
+ PolicyContent content = PolicyContents.custom(contentFields, null);
+ PolicyEntity policy1 =
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("policy1")
+ .withPolicyType("my_compaction")
+ .withEnabled(false)
+ .withExclusive(true)
+ .withInheritable(true)
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(content)
+ .withAuditInfo(testAuditInfo1)
+ .build();
+ when(policyManager.getPolicy(metalake, "policy1")).thenReturn(policy1);
+
+ Response resp =
+ target(policyPath(metalake))
+ .path("policy1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ PolicyResponse policyResp = resp.readEntity(PolicyResponse.class);
+ Assertions.assertEquals(0, policyResp.getCode());
+
+ Policy respPolicy = policyResp.getPolicy();
+ Assertions.assertEquals(policy1.name(), respPolicy.name());
+ Assertions.assertEquals(policy1.comment(), respPolicy.comment());
+ Assertions.assertEquals(Optional.empty(), respPolicy.inherited());
+
+ // Test throw NoSuchPolicyException
+ doThrow(new NoSuchPolicyException("mock error"))
+ .when(policyManager)
+ .getPolicy(metalake, "policy1");
+
+ Response resp2 =
+ target(policyPath(metalake))
+ .path("policy1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp2.getStatus());
+
+ ErrorResponse errorResp = resp2.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResp.getCode());
+ Assertions.assertEquals(NoSuchPolicyException.class.getSimpleName(),
errorResp.getType());
+
+ // Test throw RuntimeException
+ doThrow(new RuntimeException("mock
error")).when(policyManager).getPolicy(metalake, "policy1");
+
+ Response resp3 =
+ target(policyPath(metalake))
+ .path("policy1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
+
+ ErrorResponse errorResp1 = resp3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResp1.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp1.getType());
+ }
+
+ @Test
+ public void testAlterPolicy() {
+ ImmutableMap<String, Object> contentFields =
ImmutableMap.of("target_file_size_bytes", 1000);
+ PolicyContent content = PolicyContents.custom(contentFields, null);
+ PolicyEntity newPolicy =
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("new_policy1")
+ .withPolicyType("my_compaction")
+ .withComment("new policy1 comment")
+ .withEnabled(false)
+ .withExclusive(true)
+ .withInheritable(true)
+ .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+ .withContent(content)
+ .withAuditInfo(testAuditInfo1)
+ .build();
+
+ PolicyChange[] changes =
+ new PolicyChange[] {
+ PolicyChange.rename("new_policy1"),
+ PolicyChange.updateComment("new policy1 comment"),
+ PolicyChange.updateContent("my_compaction", content)
+ };
+
+ when(policyManager.alterPolicy(metalake, "policy1",
changes)).thenReturn(newPolicy);
+
+ PolicyUpdateRequest[] requests =
+ new PolicyUpdateRequest[] {
+ new PolicyUpdateRequest.RenamePolicyRequest("new_policy1"),
+ new PolicyUpdateRequest.UpdatePolicyCommentRequest("new policy1
comment"),
+ new PolicyUpdateRequest.UpdatePolicyContentRequest("my_compaction",
toDTO(content))
+ };
+ PolicyUpdatesRequest request = new
PolicyUpdatesRequest(Lists.newArrayList(requests));
+ Response resp =
+ target(policyPath(metalake))
+ .path("policy1")
+ .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());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ PolicyResponse policyResp = resp.readEntity(PolicyResponse.class);
+ Assertions.assertEquals(0, policyResp.getCode());
+
+ Policy respPolicy = policyResp.getPolicy();
+ Assertions.assertEquals(newPolicy.name(), respPolicy.name());
+ Assertions.assertEquals(newPolicy.comment(), respPolicy.comment());
+ Assertions.assertEquals(Optional.empty(), respPolicy.inherited());
+
+ // Test throw NoSuchPolicyException
+ doThrow(new NoSuchPolicyException("mock error"))
+ .when(policyManager)
+ .alterPolicy(any(), any(), any());
+
+ Response resp1 =
+ target(policyPath(metalake))
+ .path("policy1")
+ .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(),
resp1.getStatus());
+
+ ErrorResponse errorResp = resp1.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResp.getCode());
+ Assertions.assertEquals(NoSuchPolicyException.class.getSimpleName(),
errorResp.getType());
+
+ // Test throw RuntimeException
+ doThrow(new RuntimeException("mock error"))
+ .when(policyManager)
+ .alterPolicy(any(), any(), any());
+
+ Response resp2 =
+ target(policyPath(metalake))
+ .path("policy1")
+ .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(),
resp2.getStatus());
+
+ ErrorResponse errorResp1 = resp2.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResp1.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp1.getType());
+ }
+
+ @Test
+ public void testSetPolicy() {
+ PolicySetRequest req = new PolicySetRequest(true);
+ doNothing().when(policyManager).enablePolicy(any(), any());
+
+ Response resp =
+ target(policyPath(metalake))
+ .path("policy1")
+ .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 PolicySetRequest(false);
+ doNothing().when(policyManager).disablePolicy(any(), any());
+
+ Response resp1 =
+ target(policyPath(metalake))
+ .path("policy1")
+ .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(),
resp1.getStatus());
+ BaseResponse baseResponse1 = resp1.readEntity(BaseResponse.class);
+ Assertions.assertEquals(0, baseResponse1.getCode());
+ }
+
+ @Test
+ public void testDeletePolicy() {
+ when(policyManager.deletePolicy(metalake, "policy1")).thenReturn(true);
+
+ Response resp =
+ target(policyPath(metalake))
+ .path("policy1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .delete();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ DropResponse dropResp = resp.readEntity(DropResponse.class);
+ Assertions.assertEquals(0, dropResp.getCode());
+ Assertions.assertTrue(dropResp.dropped());
+
+ when(policyManager.deletePolicy(metalake, "policy1")).thenReturn(false);
+ Response resp1 =
+ target(policyPath(metalake))
+ .path("policy1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .delete();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp1.getStatus());
+
+ DropResponse dropResp1 = resp1.readEntity(DropResponse.class);
+ Assertions.assertEquals(0, dropResp1.getCode());
+ Assertions.assertFalse(dropResp1.dropped());
+
+ // Test throw RuntimeException
+ doThrow(new RuntimeException("mock
error")).when(policyManager).deletePolicy(any(), any());
+
+ Response resp2 =
+ target(policyPath(metalake))
+ .path("policy1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .delete();
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp2.getStatus());
+
+ ErrorResponse errorResp1 = resp2.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResp1.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp1.getType());
+ }
+
+ private String policyPath(String metalake) {
+ return "/metalakes/" + metalake + "/policies";
+ }
+}