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

Weird behaviour when using Kubernetes servers with a non-root api base path #284

Open
jacobtomlinson opened this issue Jan 11, 2024 · 1 comment
Labels
bug Something isn't working

Comments

@jacobtomlinson
Copy link
Member

Which project are you reporting a bug for?

kr8s

What happened?

To reproduce this bug we need a Kubernetes server running on some base URL other than /. A quick way to do this is with kubectl.

$ kubectl proxy --api-prefix=/somepath/
Starting to serve on 127.0.0.1:8001

Now if we get pods for all namespaces we see an error.

import kr8s
api = kr8s.api(url="http://127.0.0.1:8001/somepath/", namespace=kr8s.ALL)
api.get("pods")
---------------------------------------------------------------------------
HTTPStatusError                           Traceback (most recent call last)
Cell In[3], line 3
      1 import kr8s
      2 api = kr8s.api(url="http://127.0.0.1:8001/somepath/", namespace=kr8s.ALL)
----> 3 api.get("pods")

File ~/Projects/kr8s-org/kr8s/kr8s/_io.py:75, in run_sync.<locals>.wrapped(*args, **kwargs)
     73 portal = Portal()
     74 if inspect.iscoroutinefunction(coro):
---> 75     return portal.call(wrapped)
     76 raise TypeError(f"Expected coroutine function, got {coro.__class__.__name__}")

File ~/Projects/kr8s-org/kr8s/kr8s/_io.py:50, in Portal.call(self, func, *args, **kwargs)
     48 while not self._portal:
     49     pass
---> 50 return self._portal.call(func, *args, **kwargs)

File ~/miniconda3/envs/kr8s/lib/python3.11/site-packages/anyio/from_thread.py:288, in BlockingPortal.call(self, func, *args)
    273 def call(
    274     self,
    275     func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
    276     *args: Unpack[PosArgsT],
    277 ) -> T_Retval:
    278     """
    279     Call the given function in the event loop thread.
    280 
   (...)
    286 
    287     """
--> 288     return cast(T_Retval, self.start_task_soon(func, *args).result())

File ~/miniconda3/envs/kr8s/lib/python3.11/concurrent/futures/_base.py:456, in Future.result(self, timeout)
    454     raise CancelledError()
    455 elif self._state == FINISHED:
--> 456     return self.__get_result()
    457 else:
    458     raise TimeoutError()

File ~/miniconda3/envs/kr8s/lib/python3.11/concurrent/futures/_base.py:401, in Future.__get_result(self)
    399 if self._exception:
    400     try:
--> 401         raise self._exception
    402     finally:
    403         # Break a reference cycle with the exception in self._exception
    404         self = None

File ~/miniconda3/envs/kr8s/lib/python3.11/site-packages/anyio/from_thread.py:217, in BlockingPortal._call_func(self, func, args, kwargs, future)
    214         else:
    215             future.add_done_callback(callback)
--> 217         retval = await retval_or_awaitable
    218 else:
    219     retval = retval_or_awaitable

File ~/Projects/kr8s-org/kr8s/kr8s/_api.py:344, in Api.get(self, kind, namespace, label_selector, field_selector, as_object, *names, **kwargs)
    309 async def get(
    310     self,
    311     kind: Union[str, type],
   (...)
    317     **kwargs,
    318 ) -> List[object]:
    319     """
    320     Get Kubernetes resources.
    321 
   (...)
    342         The resources.
    343     """
--> 344     return await self._get(
    345         kind,
    346         *names,
    347         namespace=namespace,
    348         label_selector=label_selector,
    349         field_selector=field_selector,
    350         as_object=as_object,
    351         **kwargs,
    352     )

File ~/Projects/kr8s-org/kr8s/kr8s/_api.py:370, in Api._get(self, kind, namespace, label_selector, field_selector, as_object, *names, **kwargs)
    366     group, version = as_object.version.split("/")
    367     headers[
    368         "Accept"
    369     ] = f"application/json;as={as_object.kind};v={version};g={group}"
--> 370 async with self._get_kind(
    371     kind,
    372     namespace=namespace,
    373     label_selector=label_selector,
    374     field_selector=field_selector,
    375     headers=headers or None,
    376     **kwargs,
    377 ) as (obj_cls, response):
    378     resourcelist = response.json()
    379     if (
    380         as_object
    381         and "kind" in resourcelist
    382         and resourcelist["kind"] == as_object.kind
    383     ):

File ~/miniconda3/envs/kr8s/lib/python3.11/contextlib.py:204, in _AsyncGeneratorContextManager.__aenter__(self)
    202 del self.args, self.kwds, self.func
    203 try:
--> 204     return await anext(self.gen)
    205 except StopAsyncIteration:
    206     raise RuntimeError("generator didn't yield") from None

File ~/Projects/kr8s-org/kr8s/kr8s/_api.py:299, in Api._get_kind(self, kind, namespace, label_selector, field_selector, params, watch, **kwargs)
    297     obj_cls = get_class(kind, _asyncio=self._asyncio)
    298 params = params or None
--> 299 async with self.call_api(
    300     method="GET",
    301     url=obj_cls.endpoint,
    302     version=obj_cls.version,
    303     namespace=namespace if obj_cls.namespaced else None,
    304     params=params,
    305     **kwargs,
    306 ) as response:
    307     yield obj_cls, response

File ~/miniconda3/envs/kr8s/lib/python3.11/contextlib.py:204, in _AsyncGeneratorContextManager.__aenter__(self)
    202 del self.args, self.kwds, self.func
    203 try:
--> 204     return await anext(self.gen)
    205 except StopAsyncIteration:
    206     raise RuntimeError("generator didn't yield") from None

File ~/Projects/kr8s-org/kr8s/kr8s/_api.py:135, in Api.call_api(self, method, version, base, namespace, url, raise_for_status, stream, **kwargs)
    133         response = await self._session.request(**kwargs)
    134         if raise_for_status:
--> 135             response.raise_for_status()
    136         yield response
    137 except httpx.HTTPStatusError as e:
    138     # If we get a 401 or 403 our credentials may have expired so we
    139     # reauthenticate and try again a few times before giving up.

File ~/miniconda3/envs/kr8s/lib/python3.11/site-packages/httpx/_models.py:759, in Response.raise_for_status(self)
    757 error_type = error_types.get(status_class, "Invalid status code")
    758 message = message.format(self, error_type=error_type)
--> 759 raise HTTPStatusError(message, request=request, response=self)

HTTPStatusError: Redirect response '301 Moved Permanently' for url 'http://127.0.0.1:8001/somepath/api/v1/namespaces//pods'
Redirect location: '/somepath/api/v1/namespaces/pods'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301

Also if I omit the namespace (or set it to None) I get Pods for all namespaces, but I should only get them for the current namespace.

>>> import kr8s
>>> api = kr8s.api(url="http://127.0.0.1:8001/somepath/")
>>> for pod in api.get("pods"):
...     print(f"{pod.namespace}\t{pod.name}")
kube-system     coredns-76f75df574-5gw8n
kube-system     coredns-76f75df574-9466n
kube-system     etcd-pytest-kind-control-plane
kube-system     kindnet-t24wd
kube-system     kube-apiserver-pytest-kind-control-plane
kube-system     kube-controller-manager-pytest-kind-control-plane
kube-system     kube-proxy-wb4hm
kube-system     kube-scheduler-pytest-kind-control-plane
local-path-storage      local-path-provisioner-6f8956fb48-ccsgm

Anything else?

No response

@jacobtomlinson
Copy link
Member Author

I expect the root cause of dask/dask-kubernetes#851 is the same as this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant