This is an automated email from the ASF dual-hosted git repository.
jerryshao 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 5388cee496 [#11104] test(client-python): Add integration tests for
Owner API (#11105)
5388cee496 is described below
commit 5388cee49616852d0df886a47c511c7b47e191a7
Author: Sun Yuhan <[email protected]>
AuthorDate: Mon May 18 14:04:15 2026 +0800
[#11104] test(client-python): Add integration tests for Owner API (#11105)
### What changes were proposed in this pull request?
Add integration tests for the Python client's Owner API (`get_owner` /
`set_owner`). Also fix `OwnerDTO` to handle case-insensitive
deserialization — the server returns lowercase owner type values (e.g.,
`"user"`) which caused `ValueError` during deserialization.
### Why are the changes needed?
The Owner API only had unit tests with mocked HTTP responses.
Integration tests are needed to catch runtime issues against a real
Gravitino server with authorization enabled (e.g., the lowercase type
deserialization bug).
Fix: #11104
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
- 4 integration tests: get/set owner on metalake, catalog + schema
level, error handling
- 30 existing unit tests still pass
- pylint 10.00/10
---------
Co-authored-by: Sun Yuhan <[email protected]>
Co-authored-by: Jerry Shao <[email protected]>
---
.../gravitino/dto/authorization/owner_dto.py | 7 +-
.../client-python/tests/integration/test_owner.py | 198 +++++++++++++++++++++
.../client-python/tests/unittests/test_owner.py | 12 ++
3 files changed, 216 insertions(+), 1 deletion(-)
diff --git a/clients/client-python/gravitino/dto/authorization/owner_dto.py
b/clients/client-python/gravitino/dto/authorization/owner_dto.py
index a68cd6abb1..bf52307d13 100644
--- a/clients/client-python/gravitino/dto/authorization/owner_dto.py
+++ b/clients/client-python/gravitino/dto/authorization/owner_dto.py
@@ -28,7 +28,12 @@ class OwnerDTO(Owner):
"""Represents an Owner Data Transfer Object (DTO)."""
_name: str = field(metadata=config(field_name="name"))
- _type: Owner.Type = field(metadata=config(field_name="type"))
+ _type: Owner.Type = field(
+ metadata=config(
+ field_name="type",
+ decoder=lambda v: Owner.Type(v.upper()) if isinstance(v, str) else
v,
+ )
+ )
def name(self) -> str:
return self._name
diff --git a/clients/client-python/tests/integration/test_owner.py
b/clients/client-python/tests/integration/test_owner.py
new file mode 100644
index 0000000000..200724395a
--- /dev/null
+++ b/clients/client-python/tests/integration/test_owner.py
@@ -0,0 +1,198 @@
+# 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.
+
+import logging
+import os
+from random import randint
+
+from gravitino import (
+ GravitinoAdminClient,
+ GravitinoClient,
+ Catalog,
+)
+from gravitino.api.authorization.owner import Owner
+from gravitino.api.metadata_object import MetadataObject
+from gravitino.api.metadata_objects import MetadataObjects
+from gravitino.exceptions.base import (
+ GravitinoRuntimeException,
+ NoSuchMetadataObjectException,
+ NotFoundException,
+)
+
+from tests.integration.integration_test_env import IntegrationTestEnv
+
+logger = logging.getLogger(__name__)
+
+
+class TestOwner(IntegrationTestEnv):
+ metalake_name: str = "test_owner_metalake" + str(randint(1, 10000))
+ catalog_name: str = "test_owner_catalog" + str(randint(1, 10000))
+ test_user: str = "test_owner_user"
+
+ gravitino_admin_client: GravitinoAdminClient = None
+ gravitino_client: GravitinoClient = None
+
+ @classmethod
+ def setUpClass(cls):
+ cls._get_gravitino_home()
+ conf_path = os.path.join(cls.gravitino_home, "conf", "gravitino.conf")
+ cls._reset_conf(
+ {"gravitino.authorization.enable": "true"}, conf_path
+ )
+ cls._append_conf(
+ {"gravitino.authorization.enable": "true"}, conf_path
+ )
+ if (
+ os.environ.get("START_EXTERNAL_GRAVITINO") is not None
+ and os.environ.get("START_EXTERNAL_GRAVITINO").lower() == "true"
+ ):
+ cls.restart_server()
+ else:
+ super().setUpClass()
+ cls.gravitino_admin_client = GravitinoAdminClient(
+ uri="http://localhost:8090"
+ )
+
+ @classmethod
+ def tearDownClass(cls):
+ conf_path = os.path.join(cls.gravitino_home, "conf", "gravitino.conf")
+ cls._reset_conf(
+ {"gravitino.authorization.enable": "false"}, conf_path
+ )
+ if (
+ os.environ.get("START_EXTERNAL_GRAVITINO") is not None
+ and os.environ.get("START_EXTERNAL_GRAVITINO").lower() == "true"
+ ):
+ cls.restart_server()
+ else:
+ super().tearDownClass()
+
+ def setUp(self):
+ self.init_test_env()
+
+ def tearDown(self):
+ self.clean_test_data()
+
+ def init_test_env(self):
+ self.gravitino_admin_client.create_metalake(
+ self.metalake_name, comment="", properties={}
+ )
+ self.gravitino_client = GravitinoClient(
+ uri="http://localhost:8090", metalake_name=self.metalake_name
+ )
+
+ def create_catalog(self, catalog_name) -> Catalog:
+ return self.gravitino_client.create_catalog(
+ name=catalog_name,
+ catalog_type=Catalog.Type.FILESET,
+ provider="hadoop",
+ comment="test owner catalog",
+ properties={"location": "/tmp/test_owner"},
+ )
+
+ def clean_test_data(self):
+ self.gravitino_client = GravitinoClient(
+ uri="http://localhost:8090", metalake_name=self.metalake_name
+ )
+ try:
+ self.gravitino_client.drop_catalog(name=self.catalog_name,
force=True)
+ except GravitinoRuntimeException:
+ logger.warning("Failed to drop catalog %s", self.catalog_name)
+
+ try:
+ self.gravitino_admin_client.drop_metalake(
+ self.metalake_name, force=True
+ )
+ except GravitinoRuntimeException:
+ logger.warning("Failed to drop metalake %s", self.metalake_name)
+
+ def test_get_owner_metalake(self):
+ metalake_obj = MetadataObjects.of(
+ [self.metalake_name], MetadataObject.Type.METALAKE
+ )
+ owner = self.gravitino_client.get_owner(metalake_obj)
+ self.assertIsNotNone(owner)
+ self.assertTrue(len(owner.name()) > 0)
+ self.assertEqual(Owner.Type.USER, owner.type())
+
+ def test_set_owner_metalake(self):
+ self.gravitino_client.add_user(self.test_user)
+
+ metalake_obj = MetadataObjects.of(
+ [self.metalake_name], MetadataObject.Type.METALAKE
+ )
+ self.gravitino_client.set_owner(
+ metalake_obj, self.test_user, Owner.Type.USER
+ )
+
+ owner = self.gravitino_client.get_owner(metalake_obj)
+ self.assertIsNotNone(owner)
+ self.assertEqual(self.test_user, owner.name())
+ self.assertEqual(Owner.Type.USER, owner.type())
+
+ def test_owner_catalog_and_schema(self):
+ catalog = self.create_catalog(self.catalog_name)
+ self.gravitino_client.add_user(self.test_user)
+
+ # Test catalog-level owner
+ catalog_obj = MetadataObjects.of(
+ [self.catalog_name], MetadataObject.Type.CATALOG
+ )
+ owner = self.gravitino_client.get_owner(catalog_obj)
+ self.assertIsNotNone(owner)
+ self.assertTrue(len(owner.name()) > 0)
+ self.assertEqual(Owner.Type.USER, owner.type())
+
+ self.gravitino_client.set_owner(
+ catalog_obj, self.test_user, Owner.Type.USER
+ )
+ owner = self.gravitino_client.get_owner(catalog_obj)
+ self.assertEqual(self.test_user, owner.name())
+ self.assertEqual(Owner.Type.USER, owner.type())
+
+ # Test schema-level owner
+ schema_name = "test_owner_schema"
+ catalog.as_schemas().create_schema(schema_name, "comment", {})
+
+ schema_obj = MetadataObjects.of(
+ [self.catalog_name, schema_name], MetadataObject.Type.SCHEMA
+ )
+ self.gravitino_client.set_owner(
+ schema_obj, self.test_user, Owner.Type.USER
+ )
+
+ owner = self.gravitino_client.get_owner(schema_obj)
+ self.assertIsNotNone(owner)
+ self.assertEqual(self.test_user, owner.name())
+ self.assertEqual(Owner.Type.USER, owner.type())
+
+ def test_owner_not_found(self):
+ # Get owner for a non-existent catalog
+ fake_catalog_obj = MetadataObjects.of(
+ ["non_existent_catalog"], MetadataObject.Type.CATALOG
+ )
+ with self.assertRaises(NoSuchMetadataObjectException):
+ self.gravitino_client.get_owner(fake_catalog_obj)
+
+ # Set owner with a non-existent user
+ metalake_obj = MetadataObjects.of(
+ [self.metalake_name], MetadataObject.Type.METALAKE
+ )
+ with self.assertRaises(NotFoundException):
+ self.gravitino_client.set_owner(
+ metalake_obj, "non_existent_user", Owner.Type.USER
+ )
diff --git a/clients/client-python/tests/unittests/test_owner.py
b/clients/client-python/tests/unittests/test_owner.py
index 2da3b78487..5dcc35bb91 100644
--- a/clients/client-python/tests/unittests/test_owner.py
+++ b/clients/client-python/tests/unittests/test_owner.py
@@ -280,6 +280,18 @@ class TestOwnerDTOSerialization(unittest.TestCase):
self.assertEqual("bob", owner.name())
self.assertEqual(Owner.Type.GROUP, owner.type())
+ def test_owner_dto_deserialize_lowercase(self):
+ json_str = _json.dumps({"name": "alice", "type": "user"})
+ owner = OwnerDTO.from_json(json_str)
+ self.assertEqual("alice", owner.name())
+ self.assertEqual(Owner.Type.USER, owner.type())
+
+ def test_owner_dto_deserialize_lowercase_group(self):
+ json_str = _json.dumps({"name": "admin_group", "type": "group"})
+ owner = OwnerDTO.from_json(json_str)
+ self.assertEqual("admin_group", owner.name())
+ self.assertEqual(Owner.Type.GROUP, owner.type())
+
def test_owner_dto_round_trip(self):
original = OwnerDTO(_name="alice", _type=Owner.Type.USER)
json_str = original.to_json()