From 05927dae03ac1ef727b2864eb306c0e460c41a72 Mon Sep 17 00:00:00 2001 From: Tristan Cacqueray Date: Mon, 22 Feb 2021 22:55:43 +0000 Subject: [PATCH] kubernetes: refactor client creation to utils_k8s This change moves the kubernetes client creation to a common function to re-use the exception handling logic. Change-Id: I5bdd369f6c9a78e5f79a926d8690f285fda94af9 --- nodepool/driver/kubernetes/provider.py | 33 +---------- nodepool/driver/openshift/provider.py | 21 ++----- nodepool/driver/openshiftpods/provider.py | 29 +-------- nodepool/driver/utils_k8s.py | 59 +++++++++++++++++++ nodepool/tests/unit/test_driver_kubernetes.py | 12 ++-- nodepool/tests/unit/test_driver_openshift.py | 12 ++-- .../tests/unit/test_driver_openshiftpods.py | 12 ++-- 7 files changed, 84 insertions(+), 94 deletions(-) create mode 100644 nodepool/driver/utils_k8s.py diff --git a/nodepool/driver/kubernetes/provider.py b/nodepool/driver/kubernetes/provider.py index 57a9c7564..07d4a21a6 100644 --- a/nodepool/driver/kubernetes/provider.py +++ b/nodepool/driver/kubernetes/provider.py @@ -19,13 +19,13 @@ import urllib3 import time from kubernetes import client as k8s_client -from kubernetes import config as k8s_config from nodepool import exceptions from nodepool.driver import Provider from nodepool.driver.kubernetes import handler from nodepool.driver.utils import QuotaInformation, QuotaSupport from nodepool.driver.utils import NodeDeleter +from nodepool.driver.utils_k8s import get_client urllib3.disable_warnings() @@ -38,39 +38,12 @@ class KubernetesProvider(Provider, QuotaSupport): self.provider = provider self._zk = None self.ready = False - try: - self.k8s_client, self.rbac_client = self._get_client( - provider.context) - except k8s_config.config_exception.ConfigException: - self.log.exception("Couldn't load client from config") - self.log.info("Get context list using this command: " - "python3 -c \"from kubernetes import config; " - "print('\\n'.join([i['name'] for i in " - "config.list_kube_config_contexts()[0]]))\"") - self.k8s_client = None - self.rbac_client = None + _, _, self.k8s_client, self.rbac_client = get_client( + self.log, provider.context, k8s_client.RbacAuthorizationV1beta1Api) self.namespace_names = set() for pool in provider.pools.values(): self.namespace_names.add(pool.name) - def _get_client(self, context): - try: - conf = k8s_config.new_client_from_config(context=context) - except FileNotFoundError: - self.log.debug("Kubernetes config file not found, attempting " - "to load in-cluster configs") - conf = k8s_config.load_incluster_config() - except k8s_config.config_exception.ConfigException as e: - if 'Invalid kube-config file. No configuration found.' in str(e): - self.log.debug("Kubernetes config file not found, attempting " - "to load in-cluster configs") - conf = k8s_config.load_incluster_config() - else: - raise - return ( - k8s_client.CoreV1Api(conf), - k8s_client.RbacAuthorizationV1beta1Api(conf)) - def start(self, zk_conn): self.log.debug("Starting") self._zk = zk_conn diff --git a/nodepool/driver/openshift/provider.py b/nodepool/driver/openshift/provider.py index 6cb097da6..33a2a5b61 100644 --- a/nodepool/driver/openshift/provider.py +++ b/nodepool/driver/openshift/provider.py @@ -17,14 +17,13 @@ import logging import urllib3 import time -from kubernetes import client as k8s_client -from kubernetes import config as k8s_config -from openshift.dynamic import DynamicClient as os_client +from openshift.dynamic import DynamicClient from nodepool import exceptions from nodepool.driver import Provider from nodepool.driver.utils import NodeDeleter from nodepool.driver.openshift import handler +from nodepool.driver.utils_k8s import get_client urllib3.disable_warnings() @@ -35,24 +34,12 @@ class OpenshiftProvider(Provider): def __init__(self, provider, *args): self.provider = provider self.ready = False - try: - self.os_client, self.k8s_client = self._get_client( - provider.context) - except k8s_config.config_exception.ConfigException: - self.log.exception( - "Couldn't load context %s from config", provider.context) - self.os_client = None - self.k8s_client = None + _, _, self.k8s_client, self.os_client = get_client( + self.log, provider.context, DynamicClient) self.project_names = set() for pool in provider.pools.values(): self.project_names.add(pool.name) - def _get_client(self, context): - conf = k8s_config.new_client_from_config(context=context) - return ( - os_client(conf), - k8s_client.CoreV1Api(conf)) - def start(self, zk_conn): self.log.debug("Starting") self._zk = zk_conn diff --git a/nodepool/driver/openshiftpods/provider.py b/nodepool/driver/openshiftpods/provider.py index d1e05f6f8..c166e6bc4 100644 --- a/nodepool/driver/openshiftpods/provider.py +++ b/nodepool/driver/openshiftpods/provider.py @@ -12,14 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. -import base64 import logging import urllib3 import time -from kubernetes import client as k8s_client -from kubernetes import config as k8s_config - +from nodepool.driver.utils_k8s import get_client from nodepool.driver.utils import NodeDeleter from nodepool.driver.openshift.provider import OpenshiftProvider from nodepool.driver.openshiftpods import handler @@ -34,32 +31,12 @@ class OpenshiftPodsProvider(OpenshiftProvider): def __init__(self, provider, *args): self.provider = provider self.ready = False - try: - self.token, self.ca_crt, self.k8s_client = self._get_client( - provider.context) - except k8s_config.config_exception.ConfigException: - self.log.exception("Couldn't load client from config") - self.log.info("Get context list using this command: " - "python3 -c \"from kubernetes import config; " - "print('\\n'.join([i['name'] for i in " - "config.list_kube_config_contexts()[0]]))\"") - self.token = None - self.k8s_client = None - self.ca_crt = None + self.token, self.ca_crt, self.k8s_client, _ = get_client( + self.log, provider.context) self.pod_names = set() for pool in provider.pools.values(): self.pod_names.update(pool.labels.keys()) - def _get_client(self, context): - conf = k8s_config.new_client_from_config(context=context) - token = conf.configuration.api_key.get('authorization', '').split()[-1] - ca = None - if conf.configuration.ssl_ca_cert: - with open(conf.configuration.ssl_ca_cert) as ca_file: - ca = ca_file.read() - ca = base64.b64encode(ca.encode('utf-8')).decode('utf-8') - return (token, ca, k8s_client.CoreV1Api(conf)) - def start(self, zk_conn): self.log.debug("Starting") self._zk = zk_conn diff --git a/nodepool/driver/utils_k8s.py b/nodepool/driver/utils_k8s.py new file mode 100644 index 000000000..ef8329a3f --- /dev/null +++ b/nodepool/driver/utils_k8s.py @@ -0,0 +1,59 @@ +# Copyright (C) 2021 Red Hat +# +# Licensed 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 base64 + +from google.auth.exceptions import DefaultCredentialsError +from kubernetes import client as k8s_client +from kubernetes import config as k8s_config + + +def _get_conf(log, context): + try: + return k8s_config.new_client_from_config(context=context) + except FileNotFoundError: + log.debug("Kubernetes config file not found, attempting " + "to load in-cluster configs") + return k8s_config.load_incluster_config() + except k8s_config.config_exception.ConfigException as e: + if 'Invalid kube-config file. No configuration found.' in str(e): + log.debug("Kubernetes config file not found, attempting " + "to load in-cluster configs") + return k8s_config.load_incluster_config() + else: + raise + + +def get_client(log, context, extra_client_constructor=None): + token, ca, client, extra_client = None, None, None, None + try: + conf = _get_conf(log, context) + auth = conf.configuration.api_key.get('authorization') + if auth: + token = auth.split()[-1] + if conf.configuration.ssl_ca_cert: + with open(conf.configuration.ssl_ca_cert) as ca_file: + ca = ca_file.read() + ca = base64.b64encode(ca.encode('utf-8')).decode('utf-8') + client = k8s_client.CoreV1Api(conf) + if extra_client_constructor: + extra_client = extra_client_constructor(conf) + except DefaultCredentialsError as e: + log.error("Invalid kubernetes configuration: %s", e) + except k8s_config.config_exception.ConfigException: + log.exception( + "Couldn't load context %s from config", context) + return (token, ca, client, extra_client) diff --git a/nodepool/tests/unit/test_driver_kubernetes.py b/nodepool/tests/unit/test_driver_kubernetes.py index aba3efb28..c4ff4ecb1 100644 --- a/nodepool/tests/unit/test_driver_kubernetes.py +++ b/nodepool/tests/unit/test_driver_kubernetes.py @@ -19,7 +19,6 @@ import time from nodepool import tests from nodepool import zk -from nodepool.driver.kubernetes import provider class FakeCoreClient(object): @@ -95,13 +94,12 @@ class TestDriverKubernetes(tests.DBTestCase): self.fake_k8s_client = FakeCoreClient() self.fake_rbac_client = FakeRbacClient() - def fake_get_client(*args): - return self.fake_k8s_client, self.fake_rbac_client + def fake_get_client(log, context, ctor=None): + return None, None, self.fake_k8s_client, self.fake_rbac_client - self.useFixture(fixtures.MockPatchObject( - provider.KubernetesProvider, '_get_client', - fake_get_client - )) + self.useFixture(fixtures.MockPatch( + 'nodepool.driver.kubernetes.provider.get_client', + fake_get_client)) def test_kubernetes_machine(self): configfile = self.setup_config('kubernetes.yaml') diff --git a/nodepool/tests/unit/test_driver_openshift.py b/nodepool/tests/unit/test_driver_openshift.py index e5c624bd2..5946f8df2 100644 --- a/nodepool/tests/unit/test_driver_openshift.py +++ b/nodepool/tests/unit/test_driver_openshift.py @@ -18,7 +18,6 @@ import logging from nodepool import tests from nodepool import zk -from nodepool.driver.openshift import provider class FakeOpenshiftProjectsQuery: @@ -126,13 +125,12 @@ class TestDriverOpenshift(tests.DBTestCase): self.fake_os_client = FakeOpenshiftClient() self.fake_k8s_client = FakeCoreClient() - def fake_get_client(*args): - return self.fake_os_client, self.fake_k8s_client + def fake_get_client(log, context, ctor=None): + return None, None, self.fake_k8s_client, self.fake_os_client - self.useFixture(fixtures.MockPatchObject( - provider.OpenshiftProvider, '_get_client', - fake_get_client - )) + self.useFixture(fixtures.MockPatch( + 'nodepool.driver.openshift.provider.get_client', + fake_get_client)) def test_openshift_machine(self): configfile = self.setup_config('openshift.yaml') diff --git a/nodepool/tests/unit/test_driver_openshiftpods.py b/nodepool/tests/unit/test_driver_openshiftpods.py index b4c92002c..06aea9f46 100644 --- a/nodepool/tests/unit/test_driver_openshiftpods.py +++ b/nodepool/tests/unit/test_driver_openshiftpods.py @@ -18,7 +18,6 @@ import logging from nodepool import tests from nodepool import zk -from nodepool.driver.openshiftpods import provider class FakeCoreClient(object): @@ -75,13 +74,12 @@ class TestDriverOpenshiftPods(tests.DBTestCase): super().setUp() self.fake_k8s_client = FakeCoreClient() - def fake_get_client(*args): - return "fake-token", None, self.fake_k8s_client + def fake_get_client(log, context, ctor=None): + return "fake-token", None, self.fake_k8s_client, None - self.useFixture(fixtures.MockPatchObject( - provider.OpenshiftPodsProvider, '_get_client', - fake_get_client - )) + self.useFixture(fixtures.MockPatch( + 'nodepool.driver.openshiftpods.provider.get_client', + fake_get_client)) def test_openshift_pod(self): configfile = self.setup_config('openshiftpods.yaml')