Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client.Configuration.proxy doesn't work. #90

Open
tatsuya0619 opened this issue Jan 14, 2020 · 4 comments
Open

client.Configuration.proxy doesn't work. #90

tatsuya0619 opened this issue Jan 14, 2020 · 4 comments
Assignees

Comments

@tatsuya0619
Copy link

I was trying to use this library with http proxy.

However, if I set the 'proxy' configuration(like below script) and run the program, ka.client.CoreV1Api() raises TypeError.

This is the script(almost same as README.md sample):

import asyncio
import kubernetes_asyncio as ka


async def main():
    await ka.config.load_kube_config()

    # below 3 lines are main change from `README.md` sample.
    conf = ka.client.Configuration()
    conf.proxy = 'http://your_http_proxy'
    ka.client.Configuration.set_default(conf)

    v1 = ka.client.CoreV1Api() # This raises exception.


    print("Listing pods with their IPs:")
    ret = await v1.list_pod_for_all_namespaces()

    for i in ret.items:
        print(i.status.pod_ip, i.metadata.namespace, i.metadata.name)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.close()

The output is:

Exception ignored in: <bound method ClientSession.__del__ of <aiohttp.client.ClientSession object at 0x7fa97bb3fc88>>
Traceback (most recent call last):
  File "/home/ta-ono/.virtualenvs/dev-ka/lib/python3.6/site-packages/aiohttp/client.py", line 302, in __del__
    if not self.closed:
  File "/home/ta-ono/.virtualenvs/dev-ka/lib/python3.6/site-packages/aiohttp/client.py", line 916, in closed
    return self._connector is None or self._connector.closed
AttributeError: 'ClientSession' object has no attribute '_connector'
Traceback (most recent call last):
  File "test.py", line 26, in <module>
    loop.run_until_complete(main())
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 484, in run_until_complete
    return future.result()
  File "test.py", line 16, in main
    v1 = ka.client.CoreV1Api()
  File "/home/ta-ono/programs/kubernetes_asyncio/kubernetes_asyncio/client/api/core_v1_api.py", line 32, in __init__
    api_client = ApiClient()
  File "/home/ta-ono/programs/kubernetes_asyncio/kubernetes_asyncio/client/api_client.py", line 72, in __init__
    self.rest_client = rest.RESTClientObject(configuration)
  File "/home/ta-ono/programs/kubernetes_asyncio/kubernetes_asyncio/client/rest.py", line 76, in __init__
    proxy=configuration.proxy
TypeError: __init__() got an unexpected keyword argument 'proxy'
Exception ignored in: <bound method RESTClientObject.__del__ of <kubernetes_asyncio.client.rest.RESTClientObject object at 0x7fa97bb3f470>>
Traceback (most recent call last):
  File "/home/ta-ono/programs/kubernetes_asyncio/kubernetes_asyncio/client/rest.py", line 84, in __del__
AttributeError: 'RESTClientObject' object has no attribute 'pool_manager'

I think proxy argument should be passed to aiohttp.ClientSession.request instead of aiohttp.ClientSession.

I could run the above script by changing kubernetes_asyncio.client.rest.RESTClientObject.
The change is:

(dev-ka) [ta-ono@kubernetes_asyncio]$ git diff
diff --git a/kubernetes_asyncio/client/rest.py b/kubernetes_asyncio/client/rest.py
index 69bba34a..5a71b894 100644
--- a/kubernetes_asyncio/client/rest.py
+++ b/kubernetes_asyncio/client/rest.py
@@ -69,16 +69,12 @@ class RESTClientObject(object):
             ssl_context=ssl_context
         )

+        self.proxy = configuration.proxy
+
         # https pool manager
-        if configuration.proxy:
-            self.pool_manager = aiohttp.ClientSession(
-                connector=connector,
-                proxy=configuration.proxy
-            )
-        else:
-            self.pool_manager = aiohttp.ClientSession(
-                connector=connector
-            )
+        self.pool_manager = aiohttp.ClientSession(
+            connector=connector
+        )

     def __del__(self):
         asyncio.ensure_future(self.pool_manager.close())
@@ -168,6 +164,7 @@ class RESTClientObject(object):
                          declared content type."""
                 raise ApiException(status=0, reason=msg)

+        args['proxy'] = self.proxy
         r = await self.pool_manager.request(**args)
         if _preload_content:

However this change doesn't pass pytest via http proxy.
So I'm not sure this is correct.

Thanks.

@tomplus
Copy link
Owner

tomplus commented Jan 14, 2020

@tatsuya0619 Thanks. It looks good to me but it has to be fixed in the generator template:

https://github.com/OpenAPITools/openapi-generator/blob/50f7e14a9974cce15aaaaf5b3c4c48f079613b38/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache#L70

Could you create a PR to the openapi-generator? I can help with tests, what kind of errors have you got?

@tatsuya0619
Copy link
Author

I'm sorry, but I don't have time now to create a PR to the openapi-generator repo.

And about the pytest error, my words are incorret.
Surely, pytest passed when I don't change the test code.
However, modified pytest fails at kubernetes_asyncio/e2e_test/test_client.py.

I modified pytest code like below to communicate with Kubernetes cluster via http proxy:

(dev-ka) [ta-ono@kubernetes_asyncio]$ git diff
diff --git a/kubernetes_asyncio/e2e_test/base.py b/kubernetes_asyncio/e2e_test/base.py
index e8f1bf96..f22a0dc2 100644
--- a/kubernetes_asyncio/e2e_test/base.py
+++ b/kubernetes_asyncio/e2e_test/base.py
@@ -23,6 +23,7 @@ DEFAULT_E2E_HOST = '127.0.0.1'

 def get_e2e_configuration():
     config = Configuration()
+    config.proxy = 'http://my_http_proxy:9999'
     config.host = None
     if os.path.exists(
             os.path.expanduser(kube_config.KUBE_CONFIG_DEFAULT_LOCATION)):

I run pytest on GKE via http proxy.
And when I execute pytest, It stopped at kubernetes_asyncio/e2e_test/test_client.py. And after a while, It shows too many errors to paste it. The output of $ pytest kubernetes_asyncio/e2e_test/test_client.py with modified pytest is:

(dev-ka) [ta-ono@kubernetes_asyncio]$ pytest kubernetes_asyncio/e2e_test/test_client.py
========================================= test session starts ==========================================
platform linux -- Python 3.6.8, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
rootdir: /home/ta-ono/programs/kubernetes_asyncio
plugins: forked-1.1.3, xdist-1.31.0
collected 5 items

kubernetes_asyncio/e2e_test/test_client.py ..Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f242e608c88>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7f242e60d0b0>, 32749.447576531)]']
connector: <aiohttp.connector.TCPConnector object at 0x7f242e608ba8>
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f242e608438>
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f242e333780>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7f242e31c4c0>, 32749.591333565)]']
connector: <aiohttp.connector.TCPConnector object at 0x7f242e333748>
F                                                 [100%]

=============================================== FAILURES ===============================================
_______________________________________ TestClient.test_pod_apis _______________________________________

self = <kubernetes_asyncio.e2e_test.test_client.TestClient testMethod=test_pod_apis>

    async def test_pod_apis(self):
        client = api_client.ApiClient(configuration=self.config)
        client_ws = WsApiClient(configuration=self.config)

        api = core_v1_api.CoreV1Api(client)
        api_ws = core_v1_api.CoreV1Api(client_ws)

        name = 'busybox-test-' + short_uuid()
        pod_manifest = {
            'apiVersion': 'v1',
            'kind': 'Pod',
            'metadata': {
                'name': name
            },
            'spec': {
                'containers': [{
                    'image': 'busybox',
                    'name': 'sleep',
                    "args": [
                        "/bin/sh",
                        "-c",
                        "while true;do date;sleep 5; done"
                    ]
                }]
            }
        }

        resp = await api.create_namespaced_pod(body=pod_manifest, namespace='default')
        self.assertEqual(name, resp.metadata.name)
        self.assertTrue(resp.status.phase)

        while True:
>           resp = await api.read_namespaced_pod(name=name, namespace='default')

kubernetes_asyncio/e2e_test/test_client.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
kubernetes_asyncio/client/api_client.py:166: in __call_api
    _request_timeout=_request_timeout)
kubernetes_asyncio/client/rest.py:187: in GET
    query_params=query_params))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <kubernetes_asyncio.client.rest.RESTClientObject object at 0x7f242e608be0>, method = 'GET'
url = 'https://34.85.96.57/api/v1/namespaces/default/pods/busybox-test-596b7ff99dc4', query_params = []
headers = {'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'OpenAPI-Generator/10.0.1-snapshot/py...QoqMUS98o4L9thvCD8wREWzrYnx3bZ8sKLXhqv1iR-V9iTlEkuCT7AoYQJH7vDpFHX__30phT8WuVO_7xE5tloIc0BWzAZZrYQyWYXN-coLP9BCmS3gMo'}
body = None, post_params = {}, _preload_content = True, _request_timeout = None

    async def request(self, method, url, query_params=None, headers=None,
                      body=None, post_params=None, _preload_content=True,
                      _request_timeout=None):
        """Execute request

        :param method: http request method
        :param url: http request url
        :param query_params: query parameters in the url
        :param headers: http request headers
        :param body: request json body, for `application/json`
        :param post_params: request post parameters,
                            `application/x-www-form-urlencoded`
                            and `multipart/form-data`
        :param _preload_content: this is a non-applicable field for
                                 the AiohttpClient.
        :param _request_timeout: timeout setting for this request. If one
                                 number provided, it will be total request
                                 timeout. It can also be a pair (tuple) of
                                 (connection, read) timeouts.
        """
        method = method.upper()
        assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
                          'PATCH', 'OPTIONS']

        if post_params and body:
            raise ValueError(
                "body parameter cannot be used with post_params parameter."
            )

        post_params = post_params or {}
        headers = headers or {}
        timeout = _request_timeout or 5 * 60

        if 'Content-Type' not in headers:
            headers['Content-Type'] = 'application/json'

        args = {
            "method": method,
            "url": url,
            "timeout": timeout,
            "headers": headers
        }

        if query_params:
            args["url"] += '?' + urlencode(query_params)

        # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
        if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
            if re.search('json', headers['Content-Type'], re.IGNORECASE):
                if headers['Content-Type'] == 'application/json-patch+json':
                    if not isinstance(body, list):
                        headers['Content-Type'] = 'application/strategic-merge-patch+json'
                if body is not None:
                    body = json.dumps(body)
                args["data"] = body
            elif headers['Content-Type'] == 'application/x-www-form-urlencoded':  # noqa: E501
                args["data"] = aiohttp.FormData(post_params)
            elif headers['Content-Type'] == 'multipart/form-data':
                # must del headers['Content-Type'], or the correct
                # Content-Type which generated by aiohttp
                del headers['Content-Type']
                data = aiohttp.FormData()
                for param in post_params:
                    k, v = param
                    if isinstance(v, tuple) and len(v) == 3:
                        data.add_field(k,
                                       value=v[1],
                                       filename=v[0],
                                       content_type=v[2])
                    else:
                        data.add_field(k, v)
                args["data"] = data

            # Pass a `bytes` parameter directly in the body to support
            # other content types than Json when `body` argument is provided
            # in serialized form
            elif isinstance(body, bytes):
                args["data"] = body
            else:
                # Cannot generate the request from given parameters
                msg = """Cannot prepare a request message for provided
                         arguments. Please check that your arguments match
                         declared content type."""
                raise ApiException(status=0, reason=msg)
        args['proxy'] = self.proxy
        r = await self.pool_manager.request(**args)
        if _preload_content:

            data = await r.text()
            r = RESTResponse(r, data)

            # log response body
            logger.debug("response body: %s", r.data)

            if not 200 <= r.status <= 299:
>               raise ApiException(http_resp=r)
E               kubernetes_asyncio.client.rest.ApiException: (401)
E               Reason: Unauthorized
E               HTTP response headers: <CIMultiDictProxy('Audit-Id': '0d7f9c82-56c4-4a7e-8d19-e0319261dc48', 'Content-Type': 'application/json', 'Date': 'Fri, 17 Jan 2020 08:57:27 GMT', 'Content-Length': '129')>
E               HTTP response body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Unauthorized","reason":"Unauthorized","code":401}

kubernetes_asyncio/client/rest.py:177: ApiException
=========================================== warnings summary ===========================================
kubernetes_asyncio/e2e_test/test_client.py::TestClient::test_configmap_apis
kubernetes_asyncio/e2e_test/test_client.py::TestClient::test_node_apis
kubernetes_asyncio/e2e_test/test_client.py::TestClient::test_pod_apis
kubernetes_asyncio/e2e_test/test_client.py::TestClient::test_pod_apis
kubernetes_asyncio/e2e_test/test_client.py::TestClient::test_replication_controller_apis
kubernetes_asyncio/e2e_test/test_client.py::TestClient::test_service_apis
  /home/ta-ono/programs/kubernetes_asyncio/kubernetes_asyncio/client/rest.py:69: DeprecationWarning: ssl
_context is deprecated, use ssl=context instead
    ssl_context=ssl_context

-- Docs: https://docs.pytest.org/en/latest/warnings.html
========================= 3 failed, 2 passed, 6 warnings in 316.81s (0:05:16) ==========================
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f242e367198>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7f242e31cce0>, 32749.694242211)]']
connector: <aiohttp.connector.TCPConnector object at 0x7f242e367fd0>
(dev-ka) [ta-ono@kubernetes_asyncio]$

I couldn't spend time to research this error.
So maybe the change is wrong.

Thanks.

@ntextreme3
Copy link

Also just noticed this, opened swagger-api/swagger-codegen#9995 on generator repo.

@tomplus
Copy link
Owner

tomplus commented Jan 21, 2020

Thanks for describing the issue. I'll work on it later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants