34c91909fc
- Prevent queryformat pattern from expanding (bsc#1079048) - Fix epoch handling for Rhel 6 and 7 (bsc#1068566) - Reverting to current API for split_input - Added: * yumpkg-don-t-use-diff_attr-when-determining-install-.patch - Removed: * fix-for-wrong-version-processing.patch - Fix for wrong version processing during yum pkg install (bsc#1068566) - Older logrotate need su directive (bsc#1071322) OBS-URL: https://build.opensuse.org/request/show/573717 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=110
254 lines
9.8 KiB
Diff
254 lines
9.8 KiB
Diff
From 01c4d8875a8be8b0707b0088ccf186c4cd137448 Mon Sep 17 00:00:00 2001
|
|
From: Jochen Breuer <jbreuer@suse.de>
|
|
Date: Wed, 23 Aug 2017 21:31:28 +0200
|
|
Subject: [PATCH] Fix for delete_deployment in Kubernetes module
|
|
|
|
The Kubernetes module function delete_deployment() uses
|
|
api_instance.delete_namespaced_deployment() from the Kubernetes lib. This
|
|
method from the Kubernetes lib returns immediately without giving a success
|
|
or failure indication, which lets Salt mark the job as failed even though we
|
|
don't know if it failed or not.
|
|
|
|
To actually get a result I've implemented a polling via show_deployment() to
|
|
check if the deployment got removed.
|
|
|
|
If a time limit is hit, we are returning with an error, otherwise it is a
|
|
success.
|
|
|
|
Since Windows has no signal.alarm implementation, we are here falling back to
|
|
loop counting.
|
|
---
|
|
salt/exceptions.py | 6 ++
|
|
salt/modules/kubernetes.py | 44 +++++++++++-
|
|
tests/unit/modules/test_kubernetes.py | 126 ++++++++++++++++++++++++++++++++++
|
|
3 files changed, 175 insertions(+), 1 deletion(-)
|
|
create mode 100644 tests/unit/modules/test_kubernetes.py
|
|
|
|
diff --git a/salt/exceptions.py b/salt/exceptions.py
|
|
index 256537dd77..00111df104 100644
|
|
--- a/salt/exceptions.py
|
|
+++ b/salt/exceptions.py
|
|
@@ -265,6 +265,12 @@ class SaltCacheError(SaltException):
|
|
'''
|
|
|
|
|
|
+class TimeoutError(SaltException):
|
|
+ '''
|
|
+ Thrown when an opration cannot be completet within a given time limit.
|
|
+ '''
|
|
+
|
|
+
|
|
class SaltReqTimeoutError(SaltException):
|
|
'''
|
|
Thrown when a salt master request call fails to return within the timeout
|
|
diff --git a/salt/modules/kubernetes.py b/salt/modules/kubernetes.py
|
|
index 2e17b11444..890659c1c8 100644
|
|
--- a/salt/modules/kubernetes.py
|
|
+++ b/salt/modules/kubernetes.py
|
|
@@ -40,11 +40,15 @@ import base64
|
|
import logging
|
|
import yaml
|
|
import tempfile
|
|
+import signal
|
|
+from time import sleep
|
|
+from contextlib import contextmanager
|
|
|
|
from salt.exceptions import CommandExecutionError
|
|
from salt.ext.six import iteritems
|
|
import salt.utils
|
|
import salt.utils.templates
|
|
+from salt.ext.six.moves import range # pylint: disable=import-error
|
|
|
|
try:
|
|
import kubernetes # pylint: disable=import-self
|
|
@@ -78,6 +82,21 @@ def __virtual__():
|
|
return False, 'python kubernetes library not found'
|
|
|
|
|
|
+if not salt.utils.is_windows():
|
|
+ @contextmanager
|
|
+ def _time_limit(seconds):
|
|
+ def signal_handler(signum, frame):
|
|
+ raise TimeoutException
|
|
+ signal.signal(signal.SIGALRM, signal_handler)
|
|
+ signal.alarm(seconds)
|
|
+ try:
|
|
+ yield
|
|
+ finally:
|
|
+ signal.alarm(0)
|
|
+
|
|
+ POLLING_TIME_LIMIT = 30
|
|
+
|
|
+
|
|
# pylint: disable=no-member
|
|
def _setup_conn(**kwargs):
|
|
'''
|
|
@@ -692,7 +711,30 @@ def delete_deployment(name, namespace='default', **kwargs):
|
|
name=name,
|
|
namespace=namespace,
|
|
body=body)
|
|
- return api_response.to_dict()
|
|
+ mutable_api_response = api_response.to_dict()
|
|
+ if not salt.utils.is_windows():
|
|
+ try:
|
|
+ with _time_limit(POLLING_TIME_LIMIT):
|
|
+ while show_deployment(name, namespace) is not None:
|
|
+ sleep(1)
|
|
+ else: # pylint: disable=useless-else-on-loop
|
|
+ mutable_api_response['code'] = 200
|
|
+ except TimeoutException:
|
|
+ pass
|
|
+ else:
|
|
+ # Windows has not signal.alarm implementation, so we are just falling
|
|
+ # back to loop-counting.
|
|
+ for i in range(60):
|
|
+ if show_deployment(name, namespace) is None:
|
|
+ mutable_api_response['code'] = 200
|
|
+ break
|
|
+ else:
|
|
+ sleep(1)
|
|
+ if mutable_api_response['code'] != 200:
|
|
+ log.warning('Reached polling time limit. Deployment is not yet '
|
|
+ 'deleted, but we are backing off. Sorry, but you\'ll '
|
|
+ 'have to check manually.')
|
|
+ return mutable_api_response
|
|
except (ApiException, HTTPError) as exc:
|
|
if isinstance(exc, ApiException) and exc.status == 404:
|
|
return None
|
|
diff --git a/tests/unit/modules/test_kubernetes.py b/tests/unit/modules/test_kubernetes.py
|
|
new file mode 100644
|
|
index 0000000000..493822a93c
|
|
--- /dev/null
|
|
+++ b/tests/unit/modules/test_kubernetes.py
|
|
@@ -0,0 +1,126 @@
|
|
+# -*- coding: utf-8 -*-
|
|
+'''
|
|
+ :codeauthor: :email:`Jochen Breuer <jbreuer@suse.de>`
|
|
+'''
|
|
+
|
|
+# Import Python Libs
|
|
+from __future__ import absolute_import
|
|
+
|
|
+# Import Salt Testing Libs
|
|
+from salttesting import TestCase, skipIf
|
|
+from salttesting.mock import (
|
|
+ Mock,
|
|
+ patch,
|
|
+ NO_MOCK,
|
|
+ NO_MOCK_REASON
|
|
+)
|
|
+
|
|
+try:
|
|
+ from salt.modules import kubernetes
|
|
+except ImportError:
|
|
+ kubernetes = False
|
|
+
|
|
+# Globals
|
|
+kubernetes.__salt__ = dict()
|
|
+kubernetes.__grains__ = dict()
|
|
+kubernetes.__context__ = dict()
|
|
+
|
|
+
|
|
+@skipIf(NO_MOCK, NO_MOCK_REASON)
|
|
+@skipIf(kubernetes is False, "Probably Kubernetes client lib is not installed. \
|
|
+ Skipping test_kubernetes.py")
|
|
+class KubernetesTestCase(TestCase):
|
|
+ '''
|
|
+ Test cases for salt.modules.kubernetes
|
|
+ '''
|
|
+
|
|
+ def test_nodes(self):
|
|
+ '''
|
|
+ Test node listing.
|
|
+ :return:
|
|
+ '''
|
|
+ with patch('salt.modules.kubernetes.kubernetes') as mock_kubernetes_lib:
|
|
+ with patch.dict(kubernetes.__salt__, {'config.option': Mock(return_value="")}):
|
|
+ mock_kubernetes_lib.client.CoreV1Api.return_value = Mock(
|
|
+ **{"list_node.return_value.to_dict.return_value":
|
|
+ {'items': [{'metadata': {'name': 'mock_node_name'}}]}}
|
|
+ )
|
|
+ self.assertEqual(kubernetes.nodes(), ['mock_node_name'])
|
|
+ self.assertTrue(kubernetes.kubernetes.client.CoreV1Api().list_node().to_dict.called)
|
|
+
|
|
+ def test_deployments(self):
|
|
+ '''
|
|
+ Tests deployment listing.
|
|
+ :return:
|
|
+ '''
|
|
+ with patch('salt.modules.kubernetes.kubernetes') as mock_kubernetes_lib:
|
|
+ with patch.dict(kubernetes.__salt__, {'config.option': Mock(return_value="")}):
|
|
+ mock_kubernetes_lib.client.ExtensionsV1beta1Api.return_value = Mock(
|
|
+ **{"list_namespaced_deployment.return_value.to_dict.return_value":
|
|
+ {'items': [{'metadata': {'name': 'mock_deployment_name'}}]}}
|
|
+ )
|
|
+ self.assertEqual(kubernetes.deployments(), ['mock_deployment_name'])
|
|
+ self.assertTrue(
|
|
+ kubernetes.kubernetes.client.ExtensionsV1beta1Api().list_namespaced_deployment().to_dict.called)
|
|
+
|
|
+ def test_services(self):
|
|
+ '''
|
|
+ Tests services listing.
|
|
+ :return:
|
|
+ '''
|
|
+ with patch('salt.modules.kubernetes.kubernetes') as mock_kubernetes_lib:
|
|
+ with patch.dict(kubernetes.__salt__, {'config.option': Mock(return_value="")}):
|
|
+ mock_kubernetes_lib.client.CoreV1Api.return_value = Mock(
|
|
+ **{"list_namespaced_service.return_value.to_dict.return_value":
|
|
+ {'items': [{'metadata': {'name': 'mock_service_name'}}]}}
|
|
+ )
|
|
+ self.assertEqual(kubernetes.services(), ['mock_service_name'])
|
|
+ self.assertTrue(kubernetes.kubernetes.client.CoreV1Api().list_namespaced_service().to_dict.called)
|
|
+
|
|
+ def test_pods(self):
|
|
+ '''
|
|
+ Tests pods listing.
|
|
+ :return:
|
|
+ '''
|
|
+ with patch('salt.modules.kubernetes.kubernetes') as mock_kubernetes_lib:
|
|
+ with patch.dict(kubernetes.__salt__, {'config.option': Mock(return_value="")}):
|
|
+ mock_kubernetes_lib.client.CoreV1Api.return_value = Mock(
|
|
+ **{"list_namespaced_pod.return_value.to_dict.return_value":
|
|
+ {'items': [{'metadata': {'name': 'mock_pod_name'}}]}}
|
|
+ )
|
|
+ self.assertEqual(kubernetes.pods(), ['mock_pod_name'])
|
|
+ self.assertTrue(kubernetes.kubernetes.client.CoreV1Api().
|
|
+ list_namespaced_pod().to_dict.called)
|
|
+
|
|
+ def test_delete_deployments(self):
|
|
+ '''
|
|
+ Tests deployment deletion
|
|
+ :return:
|
|
+ '''
|
|
+ with patch('salt.modules.kubernetes.kubernetes') as mock_kubernetes_lib:
|
|
+ with patch('salt.modules.kubernetes.show_deployment', Mock(return_value=None)):
|
|
+ with patch.dict(kubernetes.__salt__, {'config.option': Mock(return_value="")}):
|
|
+ mock_kubernetes_lib.client.V1DeleteOptions = Mock(return_value="")
|
|
+ mock_kubernetes_lib.client.ExtensionsV1beta1Api.return_value = Mock(
|
|
+ **{"delete_namespaced_deployment.return_value.to_dict.return_value": {'code': ''}}
|
|
+ )
|
|
+ self.assertEqual(kubernetes.delete_deployment("test"), {'code': 200})
|
|
+ self.assertTrue(
|
|
+ kubernetes.kubernetes.client.ExtensionsV1beta1Api().
|
|
+ delete_namespaced_deployment().to_dict.called)
|
|
+
|
|
+ def test_create_deployments(self):
|
|
+ '''
|
|
+ Tests deployment creation.
|
|
+ :return:
|
|
+ '''
|
|
+ with patch('salt.modules.kubernetes.kubernetes') as mock_kubernetes_lib:
|
|
+ with patch.dict(kubernetes.__salt__, {'config.option': Mock(return_value="")}):
|
|
+ mock_kubernetes_lib.client.ExtensionsV1beta1Api.return_value = Mock(
|
|
+ **{"create_namespaced_deployment.return_value.to_dict.return_value": {}}
|
|
+ )
|
|
+ self.assertEqual(kubernetes.create_deployment("test", "default", {}, {},
|
|
+ None, None, None), {})
|
|
+ self.assertTrue(
|
|
+ kubernetes.kubernetes.client.ExtensionsV1beta1Api().
|
|
+ create_namespaced_deployment().to_dict.called)
|
|
--
|
|
2.13.6
|
|
|
|
|