salt/fix-for-delete_deployment-in-kubernetes-module.patch
Mihai Dincă 34c91909fc Accepting request 573717 from systemsmanagement:saltstack:testing
- 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
2018-02-08 11:05:40 +00:00

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