This is an automated email from the ASF dual-hosted git repository.

jshao 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 5ab6fed26 [#4208] improvement(client-python): Add Integration Test of 
OAuth2TokenProvider of client-python (#4663)
5ab6fed26 is described below

commit 5ab6fed26eee1d676f959eaf1f7ffb8c9181eae6
Author: noidname01 <[email protected]>
AuthorDate: Wed Sep 4 00:20:09 2024 +0800

    [#4208] improvement(client-python): Add Integration Test of 
OAuth2TokenProvider of client-python (#4663)
    
    ### What changes were proposed in this pull request?
    
    * Add integration test of `OAuth2TokenProvider`, following the steps
    mentioned in OAuth section of
    
[`/docs/security/how-to-authenticate.md`](https://github.com/apache/gravitino/blob/main/docs/security/how-to-authenticate.md)
    * Add some base class for containers and tests for future development
    * Remove some duplicate code
    
    ### Why are the changes needed?
    
    Fix: #4208
    
    ### Does this PR introduce _any_ user-facing change?
    
    No
    
    ### How was this patch tested?
    
    ITs added, tests with `./gradlew client:client-python:test
    -PskipDockerTests=false`
    
    ---------
    
    Co-authored-by: TimWang <[email protected]>
---
 clients/client-python/build.gradle.kts             |   1 +
 .../dto/responses/oauth2_token_response.py         |   2 -
 .../client-python/gravitino/utils/http_client.py   |   2 +-
 clients/client-python/requirements-dev.txt         |   1 +
 .../tests/integration/auth/__init__.py             |  18 +++
 .../test_auth_common.py}                           |  31 ++--
 .../tests/integration/auth/test_oauth2_client.py   | 178 +++++++++++++++++++++
 .../integration/auth/test_simple_auth_client.py    |  58 +++++++
 .../base_container.py}                             |  77 +++------
 .../tests/integration/containers/hdfs_container.py |  88 ++++++++++
 .../integration/containers/oauth2_container.py     |  69 ++++++++
 .../tests/integration/integration_test_env.py      |  72 ++++-----
 .../tests/integration/test_gvfs_with_hdfs.py       |  12 +-
 13 files changed, 483 insertions(+), 126 deletions(-)

diff --git a/clients/client-python/build.gradle.kts 
b/clients/client-python/build.gradle.kts
index 3d77039d6..625e28c14 100644
--- a/clients/client-python/build.gradle.kts
+++ b/clients/client-python/build.gradle.kts
@@ -223,6 +223,7 @@ tasks {
       "START_EXTERNAL_GRAVITINO" to "true",
       "DOCKER_TEST" to dockerTest.toString(),
       "GRAVITINO_CI_HIVE_DOCKER_IMAGE" to "apache/gravitino-ci:hive-0.1.13",
+      "GRAVITINO_OAUTH2_SAMPLE_SERVER" to 
"datastrato/sample-authorization-server:0.3.0",
       // Set the PYTHONPATH to the client-python directory, make sure the 
tests can import the
       // modules from the client-python directory.
       "PYTHONPATH" to "${project.rootDir.path}/clients/client-python"
diff --git 
a/clients/client-python/gravitino/dto/responses/oauth2_token_response.py 
b/clients/client-python/gravitino/dto/responses/oauth2_token_response.py
index 37071b723..2b81cd54f 100644
--- a/clients/client-python/gravitino/dto/responses/oauth2_token_response.py
+++ b/clients/client-python/gravitino/dto/responses/oauth2_token_response.py
@@ -44,8 +44,6 @@ class OAuth2TokenResponse(BaseResponse):
         Raise:
             IllegalArgumentException If the response is invalid, this 
exception is thrown.
         """
-        super().validate()
-
         if self._access_token is None:
             raise IllegalArgumentException("Invalid access token: None")
 
diff --git a/clients/client-python/gravitino/utils/http_client.py 
b/clients/client-python/gravitino/utils/http_client.py
index 678942bb4..696fe415c 100644
--- a/clients/client-python/gravitino/utils/http_client.py
+++ b/clients/client-python/gravitino/utils/http_client.py
@@ -140,7 +140,7 @@ class HTTPClient:
         except HTTPError as err:
             err_body = err.read()
 
-            if err_body is None:
+            if err_body is None or len(err_body) == 0:
                 return (
                     False,
                     ErrorResponse.generate_error_response(RESTException, 
err.reason),
diff --git a/clients/client-python/requirements-dev.txt 
b/clients/client-python/requirements-dev.txt
index e91d966a4..002e08964 100644
--- a/clients/client-python/requirements-dev.txt
+++ b/clients/client-python/requirements-dev.txt
@@ -29,3 +29,4 @@ cachetools==5.3.3
 readerwriterlock==1.0.9
 docker==7.1.0
 pyjwt[crypto]==2.8.0
+jwcrypto==1.5.6
diff --git a/clients/client-python/tests/integration/auth/__init__.py 
b/clients/client-python/tests/integration/auth/__init__.py
new file mode 100644
index 000000000..c206137f1
--- /dev/null
+++ b/clients/client-python/tests/integration/auth/__init__.py
@@ -0,0 +1,18 @@
+"""
+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.
+"""
diff --git a/clients/client-python/tests/integration/test_simple_auth_client.py 
b/clients/client-python/tests/integration/auth/test_auth_common.py
similarity index 86%
rename from clients/client-python/tests/integration/test_simple_auth_client.py
rename to clients/client-python/tests/integration/auth/test_auth_common.py
index 062e03e9d..57c377f78 100644
--- a/clients/client-python/tests/integration/test_simple_auth_client.py
+++ b/clients/client-python/tests/integration/auth/test_auth_common.py
@@ -29,15 +29,17 @@ from gravitino import (
     Catalog,
     Fileset,
 )
-from gravitino.auth.simple_auth_provider import SimpleAuthProvider
 from gravitino.exceptions.base import GravitinoRuntimeException
-from tests.integration.integration_test_env import IntegrationTestEnv
 
 logger = logging.getLogger(__name__)
 
 
-class TestSimpleAuthClient(IntegrationTestEnv):
-    creator: str = "test_client"
+class TestCommonAuth:
+    """
+    A common test set for AuthProvider Integration Tests
+    """
+
+    creator: str = "test"
     metalake_name: str = "TestClient_metalake" + str(randint(1, 10000))
     catalog_name: str = "fileset_catalog"
     catalog_location_prop: str = "location"  # Fileset Catalog must set 
`location`
@@ -63,16 +65,8 @@ class TestSimpleAuthClient(IntegrationTestEnv):
         metalake_name, catalog_name, schema_name
     )
     fileset_ident: NameIdentifier = NameIdentifier.of(schema_name, 
fileset_name)
-
-    def setUp(self):
-        os.environ["GRAVITINO_USER"] = self.creator
-        self.gravitino_admin_client = GravitinoAdminClient(
-            uri="http://localhost:8090";, 
auth_data_provider=SimpleAuthProvider()
-        )
-        self.init_test_env()
-
-    def tearDown(self):
-        self.clean_test_data()
+    gravitino_admin_client: GravitinoAdminClient
+    gravitino_client: GravitinoClient
 
     def clean_test_data(self):
         catalog = self.gravitino_client.load_catalog(name=self.catalog_name)
@@ -117,14 +111,7 @@ class TestSimpleAuthClient(IntegrationTestEnv):
         os.environ["GRAVITINO_USER"] = ""
 
     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,
-            auth_data_provider=SimpleAuthProvider(),
-        )
+
         catalog = self.gravitino_client.create_catalog(
             name=self.catalog_name,
             catalog_type=Catalog.Type.FILESET,
diff --git a/clients/client-python/tests/integration/auth/test_oauth2_client.py 
b/clients/client-python/tests/integration/auth/test_oauth2_client.py
new file mode 100644
index 000000000..3db7232b3
--- /dev/null
+++ b/clients/client-python/tests/integration/auth/test_oauth2_client.py
@@ -0,0 +1,178 @@
+"""
+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 os
+import subprocess
+import logging
+import unittest
+import sys
+import requests
+from jwcrypto import jwk
+
+from gravitino.auth.auth_constants import AuthConstants
+from gravitino.auth.default_oauth2_token_provider import 
DefaultOAuth2TokenProvider
+from gravitino import GravitinoAdminClient, GravitinoClient
+from gravitino.exceptions.base import GravitinoRuntimeException
+
+from tests.integration.auth.test_auth_common import TestCommonAuth
+from tests.integration.integration_test_env import (
+    IntegrationTestEnv,
+    check_gravitino_server_status,
+)
+from tests.integration.containers.oauth2_container import OAuth2Container
+
+logger = logging.getLogger(__name__)
+
+DOCKER_TEST = os.environ.get("DOCKER_TEST")
+
+
[email protected](
+    DOCKER_TEST == "false",
+    "Skipping tests when DOCKER_TEST=false",
+)
+class TestOAuth2(IntegrationTestEnv, TestCommonAuth):
+
+    oauth2_container: OAuth2Container = None
+
+    @classmethod
+    def setUpClass(cls):
+
+        cls._get_gravitino_home()
+
+        cls.oauth2_container = OAuth2Container()
+        cls.oauth2_container_ip = cls.oauth2_container.get_ip()
+
+        cls.oauth2_server_uri = f"http://{cls.oauth2_container_ip}:8177";
+
+        # Get PEM from OAuth Server
+        default_sign_key = cls._get_default_sign_key()
+
+        cls.config = {
+            "gravitino.authenticators": "oauth",
+            "gravitino.authenticator.oauth.serviceAudience": "test",
+            "gravitino.authenticator.oauth.defaultSignKey": default_sign_key,
+            "gravitino.authenticator.oauth.serverUri": cls.oauth2_server_uri,
+            "gravitino.authenticator.oauth.tokenPath": "/oauth2/token",
+        }
+
+        cls.oauth2_conf_path = f"{cls.gravitino_home}/conf/gravitino.conf"
+
+        # append the hadoop conf to server
+        cls._append_conf(cls.config, cls.oauth2_conf_path)
+        # restart the server
+        cls._restart_server_with_oauth()
+
+    @classmethod
+    def tearDownClass(cls):
+        try:
+            # reset server conf
+            cls._reset_conf(cls.config, cls.oauth2_conf_path)
+            # restart server
+            cls.restart_server()
+        finally:
+            # close oauth2 container
+            cls.oauth2_container.close()
+
+    @classmethod
+    def _get_default_sign_key(cls) -> str:
+
+        jwk_uri = f"{cls.oauth2_server_uri}/oauth2/jwks"
+
+        # Get JWK from OAuth2 Server
+        res = requests.get(jwk_uri).json()
+        key = res["keys"][0]
+
+        # Convert JWK to PEM
+        pem = jwk.JWK(**key).export_to_pem().decode("utf-8")
+
+        default_sign_key = "".join(pem.split("\n")[1:-2])
+
+        return default_sign_key
+
+    @classmethod
+    def _restart_server_with_oauth(cls):
+        logger.info("Restarting Gravitino server...")
+        gravitino_home = os.environ.get("GRAVITINO_HOME")
+        gravitino_startup_script = os.path.join(gravitino_home, 
"bin/gravitino.sh")
+        if not os.path.exists(gravitino_startup_script):
+            raise GravitinoRuntimeException(
+                f"Can't find Gravitino startup script: 
{gravitino_startup_script}, "
+                "Please execute `./gradlew compileDistribution -x test` in the 
Gravitino "
+                "project root directory."
+            )
+
+        # Restart Gravitino Server
+        env_vars = os.environ.copy()
+        result = subprocess.run(
+            [gravitino_startup_script, "restart"],
+            env=env_vars,
+            capture_output=True,
+            text=True,
+            check=False,
+        )
+        if result.stdout:
+            logger.info("stdout: %s", result.stdout)
+        if result.stderr:
+            logger.info("stderr: %s", result.stderr)
+
+        oauth2_token_provider = DefaultOAuth2TokenProvider(
+            f"{cls.oauth2_server_uri}", "test:test", "test", "oauth2/token"
+        )
+
+        auth_header = {
+            AuthConstants.HTTP_HEADER_AUTHORIZATION: 
oauth2_token_provider.get_token_data().decode(
+                "utf-8"
+            )
+        }
+
+        if not check_gravitino_server_status(headers=auth_header):
+            logger.error("ERROR: Can't start Gravitino server!")
+            sys.exit(0)
+
+    def setUp(self):
+        oauth2_token_provider = DefaultOAuth2TokenProvider(
+            f"{self.oauth2_server_uri}", "test:test", "test", "oauth2/token"
+        )
+
+        self.gravitino_admin_client = GravitinoAdminClient(
+            uri="http://localhost:8090";, 
auth_data_provider=oauth2_token_provider
+        )
+
+        self.init_test_env()
+
+    def init_test_env(self):
+
+        self.gravitino_admin_client.create_metalake(
+            self.metalake_name, comment="", properties={}
+        )
+
+        oauth2_token_provider = DefaultOAuth2TokenProvider(
+            f"{self.oauth2_server_uri}", "test:test", "test", "oauth2/token"
+        )
+
+        self.gravitino_client = GravitinoClient(
+            uri="http://localhost:8090";,
+            metalake_name=self.metalake_name,
+            auth_data_provider=oauth2_token_provider,
+        )
+
+        super().init_test_env()
+
+    def tearDown(self):
+        self.clean_test_data()
diff --git 
a/clients/client-python/tests/integration/auth/test_simple_auth_client.py 
b/clients/client-python/tests/integration/auth/test_simple_auth_client.py
new file mode 100644
index 000000000..6533e49a7
--- /dev/null
+++ b/clients/client-python/tests/integration/auth/test_simple_auth_client.py
@@ -0,0 +1,58 @@
+"""
+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 gravitino import (
+    GravitinoClient,
+    GravitinoAdminClient,
+)
+from gravitino.auth.simple_auth_provider import SimpleAuthProvider
+
+from tests.integration.auth.test_auth_common import TestCommonAuth
+from tests.integration.integration_test_env import IntegrationTestEnv
+
+logger = logging.getLogger(__name__)
+
+
+class TestSimpleAuthClient(IntegrationTestEnv, TestCommonAuth):
+
+    def setUp(self):
+        os.environ["GRAVITINO_USER"] = self.creator
+        self.gravitino_admin_client = GravitinoAdminClient(
+            uri="http://localhost:8090";, 
auth_data_provider=SimpleAuthProvider()
+        )
+
+        self.init_test_env()
+
+    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,
+            auth_data_provider=SimpleAuthProvider(),
+        )
+
+        super().init_test_env()
+
+    def tearDown(self):
+        self.clean_test_data()
diff --git a/clients/client-python/tests/integration/hdfs_container.py 
b/clients/client-python/tests/integration/containers/base_container.py
similarity index 59%
rename from clients/client-python/tests/integration/hdfs_container.py
rename to clients/client-python/tests/integration/containers/base_container.py
index 16cb2a80c..83d03b277 100644
--- a/clients/client-python/tests/integration/hdfs_container.py
+++ b/clients/client-python/tests/integration/containers/base_container.py
@@ -17,72 +17,33 @@ specific language governing permissions and limitations
 under the License.
 """
 
-import asyncio
 import logging
-import os
-import time
+from typing import Dict
 
 import docker
 from docker import types as tp
-from docker.errors import NotFound, DockerException
+from docker.errors import NotFound
 
 from gravitino.exceptions.base import GravitinoRuntimeException
-from gravitino.exceptions.base import InternalError
 
 logger = logging.getLogger(__name__)
 
 
-async def check_hdfs_status(hdfs_container):
-    retry_limit = 15
-    for _ in range(retry_limit):
-        try:
-            command_and_args = ["bash", "/tmp/check-status.sh"]
-            exec_result = hdfs_container.exec_run(command_and_args)
-            if exec_result.exit_code != 0:
-                message = (
-                    f"Command {command_and_args} exited with 
{exec_result.exit_code}"
-                )
-                logger.warning(message)
-                logger.warning("output: %s", exec_result.output)
-                output_status_command = ["hdfs", "dfsadmin", "-report"]
-                exec_result = hdfs_container.exec_run(output_status_command)
-                logger.info("HDFS report, output: %s", exec_result.output)
-            else:
-                logger.info("HDFS startup successfully!")
-                return True
-        except DockerException as e:
-            logger.error(
-                "Exception occurred while checking HDFS container status: %s", 
e
-            )
-        time.sleep(10)
-    return False
-
-
-async def check_hdfs_container_status(hdfs_container):
-    timeout_sec = 150
-    try:
-        result = await asyncio.wait_for(
-            check_hdfs_status(hdfs_container), timeout=timeout_sec
-        )
-        if not result:
-            raise InternalError("HDFS container startup failed!")
-    except asyncio.TimeoutError as e:
-        raise GravitinoRuntimeException(
-            "Timeout occurred while waiting for checking HDFS container 
status."
-        ) from e
-
-
-class HDFSContainer:
+class BaseContainer:
     _docker_client = None
     _container = None
     _network = None
     _ip = ""
     _network_name = "python-net"
-    _container_name = "python-hdfs"
+    _container_name: str
 
-    def __init__(self):
+    def __init__(
+        self, container_name: str, image_name: str, enviroment: Dict = None, 
**kwarg
+    ):
+        self._container_name = container_name
         self._docker_client = docker.from_env()
         self._create_networks()
+
         try:
             container = 
self._docker_client.containers.get(self._container_name)
             if container is not None:
@@ -90,23 +51,19 @@ class HDFSContainer:
                     container.restart()
                 self._container = container
         except NotFound:
-            logger.warning("Cannot find hdfs container in docker env, skip 
remove.")
+            logger.warning(
+                "Cannot find the container %s in docker env, skip remove.",
+                self._container_name,
+            )
         if self._container is None:
-            image_name = os.environ.get("GRAVITINO_CI_HIVE_DOCKER_IMAGE")
-            if image_name is None:
-                raise GravitinoRuntimeException(
-                    "GRAVITINO_CI_HIVE_DOCKER_IMAGE env variable is not set."
-                )
             self._container = self._docker_client.containers.run(
                 image=image_name,
                 name=self._container_name,
                 detach=True,
-                environment={"HADOOP_USER_NAME": "anonymous"},
+                environment=enviroment,
                 network=self._network_name,
+                **kwarg,
             )
-        asyncio.run(check_hdfs_container_status(self._container))
-
-        self._fetch_ip()
 
     def _create_networks(self):
         pool_config = tp.IPAMPool(subnet="10.20.31.16/28")
@@ -123,7 +80,9 @@ class HDFSContainer:
 
     def _fetch_ip(self):
         if self._container is None:
-            raise GravitinoRuntimeException("The HDFS container has not init.")
+            raise GravitinoRuntimeException(
+                f"The container {self._container_name} has not init."
+            )
 
         container_info = 
self._docker_client.api.inspect_container(self._container.id)
         self._ip = 
container_info["NetworkSettings"]["Networks"][self._network_name][
diff --git 
a/clients/client-python/tests/integration/containers/hdfs_container.py 
b/clients/client-python/tests/integration/containers/hdfs_container.py
new file mode 100644
index 000000000..8676f6736
--- /dev/null
+++ b/clients/client-python/tests/integration/containers/hdfs_container.py
@@ -0,0 +1,88 @@
+"""
+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 asyncio
+import logging
+import os
+import time
+
+from docker.errors import DockerException
+from gravitino.exceptions.base import GravitinoRuntimeException
+from gravitino.exceptions.base import InternalError
+
+from tests.integration.containers.base_container import BaseContainer
+
+logger = logging.getLogger(__name__)
+
+
+async def check_hdfs_status(hdfs_container):
+    retry_limit = 15
+    for _ in range(retry_limit):
+        try:
+            command_and_args = ["bash", "/tmp/check-status.sh"]
+            exec_result = hdfs_container.exec_run(command_and_args)
+            if exec_result.exit_code != 0:
+                message = (
+                    f"Command {command_and_args} exited with 
{exec_result.exit_code}"
+                )
+                logger.warning(message)
+                logger.warning("output: %s", exec_result.output)
+                output_status_command = ["hdfs", "dfsadmin", "-report"]
+                exec_result = hdfs_container.exec_run(output_status_command)
+                logger.info("HDFS report, output: %s", exec_result.output)
+            else:
+                logger.info("HDFS startup successfully!")
+                return True
+        except DockerException as e:
+            logger.error(
+                "Exception occurred while checking HDFS container status: %s", 
e
+            )
+        time.sleep(10)
+    return False
+
+
+async def check_hdfs_container_status(hdfs_container):
+    timeout_sec = 150
+    try:
+        result = await asyncio.wait_for(
+            check_hdfs_status(hdfs_container), timeout=timeout_sec
+        )
+        if not result:
+            raise InternalError("HDFS container startup failed!")
+    except asyncio.TimeoutError as e:
+        raise GravitinoRuntimeException(
+            "Timeout occurred while waiting for checking HDFS container 
status."
+        ) from e
+
+
+class HDFSContainer(BaseContainer):
+
+    def __init__(self):
+        container_name = "python-hdfs"
+        image_name = os.environ.get("GRAVITINO_CI_HIVE_DOCKER_IMAGE")
+        if image_name is None:
+            raise GravitinoRuntimeException(
+                "GRAVITINO_CI_HIVE_DOCKER_IMAGE env variable is not set."
+            )
+        environment = {"HADOOP_USER_NAME": "anonymous"}
+
+        super().__init__(container_name, image_name, environment)
+
+        asyncio.run(check_hdfs_container_status(self._container))
+        self._fetch_ip()
diff --git 
a/clients/client-python/tests/integration/containers/oauth2_container.py 
b/clients/client-python/tests/integration/containers/oauth2_container.py
new file mode 100644
index 000000000..763e23619
--- /dev/null
+++ b/clients/client-python/tests/integration/containers/oauth2_container.py
@@ -0,0 +1,69 @@
+"""
+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 asyncio
+import os
+import time
+
+from gravitino.exceptions.base import GravitinoRuntimeException
+
+from tests.integration.containers.base_container import BaseContainer
+
+TIEMOUT_SEC = 5
+RETRY_LIMIT = 30
+
+
+async def check_oauth2_container_status(oauth2_container: "OAuth2Container"):
+    for _ in range(RETRY_LIMIT):
+        if oauth2_container.health_check():
+            return True
+        time.sleep(TIEMOUT_SEC)
+    return False
+
+
+class OAuth2Container(BaseContainer):
+
+    def __init__(self):
+        container_name = "sample-auth-server"
+        image_name = os.environ.get("GRAVITINO_OAUTH2_SAMPLE_SERVER")
+        if image_name is None:
+            raise GravitinoRuntimeException(
+                "GRAVITINO_OAUTH2_SAMPLE_SERVER env variable is not set."
+            )
+
+        healthcheck = {
+            "test": [
+                "CMD-SHELL",
+                "wget -qO - http://localhost:8177/oauth2/jwks || exit 1",
+            ],
+            "interval": TIEMOUT_SEC * 1000000000,
+            "retries": RETRY_LIMIT,
+        }
+
+        super().__init__(container_name, image_name, healthcheck=healthcheck)
+        asyncio.run(check_oauth2_container_status(self))
+        self._fetch_ip()
+
+    def health_check(self) -> bool:
+        return (
+            
self._docker_client.api.inspect_container(self._container_name)["State"][
+                "Health"
+            ]["Status"]
+            == "healthy"
+        )
diff --git a/clients/client-python/tests/integration/integration_test_env.py 
b/clients/client-python/tests/integration/integration_test_env.py
index 7b8c05f53..4263d5af4 100644
--- a/clients/client-python/tests/integration/integration_test_env.py
+++ b/clients/client-python/tests/integration/integration_test_env.py
@@ -31,9 +31,9 @@ from gravitino.exceptions.base import 
GravitinoRuntimeException
 logger = logging.getLogger(__name__)
 
 
-def get_gravitino_server_version():
+def get_gravitino_server_version(**kwargs):
     try:
-        response = requests.get("http://localhost:8090/api/version";)
+        response = requests.get("http://localhost:8090/api/version";, **kwargs)
         response.raise_for_status()  # raise an exception for bad status codes
         response.close()
         return True
@@ -42,11 +42,11 @@ def get_gravitino_server_version():
         return False
 
 
-def check_gravitino_server_status() -> bool:
+def check_gravitino_server_status(**kwargs) -> bool:
     gravitino_server_running = False
     for i in range(5):
         logger.info("Monitoring Gravitino server status. Attempt %s", i + 1)
-        if get_gravitino_server_version():
+        if get_gravitino_server_version(**kwargs):
             logger.debug("Gravitino Server is running")
             gravitino_server_running = True
             break
@@ -69,14 +69,10 @@ class IntegrationTestEnv(unittest.TestCase):
                 logger.error("ERROR: Can't find online Gravitino server!")
             return
 
-        gravitino_home = os.environ.get("GRAVITINO_HOME")
-        if gravitino_home is None:
-            logger.error(
-                "Gravitino Python client integration test must configure 
`GRAVITINO_HOME`"
-            )
-            sys.exit(0)
-
-        cls.gravitino_startup_script = os.path.join(gravitino_home, 
"bin/gravitino.sh")
+        cls._get_gravitino_home()
+        cls.gravitino_startup_script = os.path.join(
+            cls.gravitino_home, "bin/gravitino.sh"
+        )
         if not os.path.exists(cls.gravitino_startup_script):
             logger.error(
                 "Can't find Gravitino startup script: %s, "
@@ -166,37 +162,35 @@ class IntegrationTestEnv(unittest.TestCase):
             raise GravitinoRuntimeException("ERROR: Can't start Gravitino 
server!")
 
     @classmethod
-    def _append_catalog_hadoop_conf(cls, config):
-        logger.info("Append catalog hadoop conf.")
+    def _get_gravitino_home(cls):
         gravitino_home = os.environ.get("GRAVITINO_HOME")
         if gravitino_home is None:
-            raise GravitinoRuntimeException("Cannot find GRAVITINO_HOME env.")
-        hadoop_conf_path = f"{gravitino_home}/catalogs/hadoop/conf/hadoop.conf"
-        if not os.path.exists(hadoop_conf_path):
-            raise GravitinoRuntimeException(
-                f"Hadoop conf file is not found at `{hadoop_conf_path}`."
+            logger.error(
+                "Gravitino Python client integration test must configure 
`GRAVITINO_HOME`"
             )
+            sys.exit(0)
+
+        cls.gravitino_home = gravitino_home
 
-        with open(hadoop_conf_path, mode="a", encoding="utf-8") as f:
+    @classmethod
+    def _append_conf(cls, config, conf_path):
+        logger.info("Append %s.", conf_path)
+        if not os.path.exists(conf_path):
+            raise GravitinoRuntimeException(f"Conf file is not found at 
`{conf_path}`.")
+
+        with open(conf_path, mode="a", encoding="utf-8") as f:
             for key, value in config.items():
                 f.write(f"\n{key} = {value}")
 
     @classmethod
-    def _reset_catalog_hadoop_conf(cls, config):
-        logger.info("Reset catalog hadoop conf.")
-        gravitino_home = os.environ.get("GRAVITINO_HOME")
-        if gravitino_home is None:
-            raise GravitinoRuntimeException("Cannot find GRAVITINO_HOME env.")
-        hadoop_conf_path = f"{gravitino_home}/catalogs/hadoop/conf/hadoop.conf"
-        if not os.path.exists(hadoop_conf_path):
-            raise GravitinoRuntimeException(
-                f"Hadoop conf file is not found at `{hadoop_conf_path}`."
-            )
+    def _reset_conf(cls, config, conf_path):
+        logger.info("Reset %s.", conf_path)
+        if not os.path.exists(conf_path):
+            raise GravitinoRuntimeException(f"Conf file is not found at 
`{conf_path}`.")
         filtered_lines = []
-        with open(hadoop_conf_path, mode="r", encoding="utf-8") as file:
+        with open(conf_path, mode="r", encoding="utf-8") as file:
             origin_lines = file.readlines()
 
-        existed_config = {}
         for line in origin_lines:
             line = line.strip()
             if line.startswith("#"):
@@ -205,16 +199,16 @@ class IntegrationTestEnv(unittest.TestCase):
             else:
                 try:
                     key, value = line.split("=")
-                    existed_config[key.strip()] = value.strip()
+                    key = key.strip()
+                    value = value.strip()
+                    if key not in config:
+                        append_line = f"{key} = {value}\n"
+                        filtered_lines.append(append_line)
+
                 except ValueError:
                     # cannot split to key, value, so just append
                     filtered_lines.append(line + "\n")
 
-        for key, value in existed_config.items():
-            if config[key] is None:
-                append_line = f"{key} = {value}\n"
-                filtered_lines.append(append_line)
-
-        with open(hadoop_conf_path, mode="w", encoding="utf-8") as file:
+        with open(conf_path, mode="w", encoding="utf-8") as file:
             for line in filtered_lines:
                 file.write(line)
diff --git a/clients/client-python/tests/integration/test_gvfs_with_hdfs.py 
b/clients/client-python/tests/integration/test_gvfs_with_hdfs.py
index 93682fa57..af73b354d 100644
--- a/clients/client-python/tests/integration/test_gvfs_with_hdfs.py
+++ b/clients/client-python/tests/integration/test_gvfs_with_hdfs.py
@@ -46,7 +46,7 @@ from gravitino import (
 from gravitino.auth.auth_constants import AuthConstants
 from gravitino.exceptions.base import GravitinoRuntimeException
 from tests.integration.integration_test_env import IntegrationTestEnv
-from tests.integration.hdfs_container import HDFSContainer
+from tests.integration.containers.hdfs_container import HDFSContainer
 from tests.integration.base_hadoop_env import BaseHadoopEnvironment
 
 logger = logging.getLogger(__name__)
@@ -94,6 +94,9 @@ class TestGvfsWithHDFS(IntegrationTestEnv):
 
     @classmethod
     def setUpClass(cls):
+
+        cls._get_gravitino_home()
+
         cls.hdfs_container = HDFSContainer()
         hdfs_container_ip = cls.hdfs_container.get_ip()
         # init hadoop env
@@ -101,8 +104,11 @@ class TestGvfsWithHDFS(IntegrationTestEnv):
         cls.config = {
             "gravitino.bypass.fs.defaultFS": f"hdfs://{hdfs_container_ip}:9000"
         }
+
+        cls.hadoop_conf_path = 
f"{cls.gravitino_home}/catalogs/hadoop/conf/hadoop.conf"
+
         # append the hadoop conf to server
-        cls._append_catalog_hadoop_conf(cls.config)
+        cls._append_conf(cls.config, cls.hadoop_conf_path)
         # restart the server
         cls.restart_server()
         # create entity
@@ -113,7 +119,7 @@ class TestGvfsWithHDFS(IntegrationTestEnv):
         try:
             cls._clean_test_data()
             # reset server conf
-            cls._reset_catalog_hadoop_conf(cls.config)
+            cls._reset_conf(cls.config, cls.hadoop_conf_path)
             # restart server
             cls.restart_server()
             # clear hadoop env


Reply via email to