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

share kubernetes client instances #128

Merged
merged 3 commits into from
Feb 23, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions kubespawner/clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import weakref

import kubernetes.client

_client_cache = {}


def shared_client(ClientType, *args, **kwargs):
"""Return a single shared kubernetes client instance

A weak reference to the instance is cached,
so that concurrent calls to shared_client
will all return the same instance until
all references to the client are cleared.
"""
kwarg_key = tuple((key, kwargs[key]) for key in sorted(kwargs))
cache_key = (ClientType, args, kwarg_key)
client = None
if cache_key in _client_cache:
client = _client_cache[cache_key]()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the trailing ()? I think _client_cache contains instances already so this would end up calling __call__ on that instance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_client_cache contains weak references to instances, not the instances themselves. This allows them to be garbage collected. The ref must be called in order to return the instance. If it has been garbage collected in the meantime, resolving the weakref will return None.

I've added a comment to clarify what's happening here.

I've


if client is None:
Client = getattr(kubernetes.client, ClientType)
client = Client(*args, **kwargs)
# cache weakref so that clients can be garbage collected
_client_cache[cache_key] = weakref.ref(client)
return client
7 changes: 4 additions & 3 deletions kubespawner/proxy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from concurrent.futures import ThreadPoolExecutor
import os
import string
import escapism
Expand All @@ -10,7 +11,7 @@
from kubespawner.objects import make_ingress
from kubespawner.utils import generate_hashed_slug
from kubespawner.reflector import NamespacedResourceReflector
from concurrent.futures import ThreadPoolExecutor
from .clients import shared_client
from traitlets import Unicode
from tornado import gen
from tornado.concurrent import run_on_executor
Expand Down Expand Up @@ -96,8 +97,8 @@ def __init__(self, *args, **kwargs):
self.service_reflector = ServiceReflector(parent=self, namespace=self.namespace)
self.endpoint_reflector = EndpointsReflector(parent=self, namespace=self.namespace)

self.core_api = client.CoreV1Api()
self.extension_api = client.ExtensionsV1beta1Api()
self.core_api = shared_client('CoreV1Api')
self.extension_api = shared_client('ExtensionsV1beta1Api')

@run_on_executor
def asynchronize(self, method, *args, **kwargs):
Expand Down
7 changes: 2 additions & 5 deletions kubespawner/spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@
"""
import os
import json
import time
import string
import threading
import sys
from urllib.parse import urlparse, urlunparse
import json
import multiprocessing
from concurrent.futures import ThreadPoolExecutor

Expand All @@ -26,6 +22,7 @@
from kubernetes import client
import escapism

from .clients import shared_client
from kubespawner.traitlets import Callable
from kubespawner.utils import Callable
from kubespawner.objects import make_pod, make_pvc
Expand Down Expand Up @@ -80,7 +77,7 @@ def on_reflector_failure():
on_failure=on_reflector_failure
)

self.api = client.CoreV1Api()
self.api = shared_client('CoreV1Api')

self.pod_name = self._expand_user_properties(self.pod_name_template)
self.pvc_name = self._expand_user_properties(self.pvc_name_template)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
install_requires=[
'jupyterhub>=0.8',
'pyYAML',
'kubernetes==3.*',
'kubernetes==4.*',
'escapism',
],
setup_requires=['pytest-runner'],
Expand Down