salt/tests/unit/states/test_kubernetes.py
Pedro Algarvio 4d49013247 Update to isort 5.10.1
Signed-off-by: Pedro Algarvio <palgarvio@vmware.com>
2022-08-01 12:49:16 -06:00

897 lines
34 KiB
Python

"""
:codeauthor: :email:`Jeff Schroeder <jeffschroeder@computer.org>`
"""
import base64
from contextlib import contextmanager
import pytest
import salt.modules.kubernetesmod as kubernetesmod
import salt.states.kubernetes as kubernetes
import salt.utils.stringutils
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import MagicMock, patch
from tests.support.unit import TestCase
@pytest.mark.skipif(
kubernetesmod.HAS_LIBS is False,
reason="Probably Kubernetes client lib is not installed. Skipping test_kubernetes.py",
)
class KubernetesTestCase(TestCase, LoaderModuleMockMixin):
"""
Test cases for salt.states.kubernetes
"""
def setup_loader_modules(self):
return {kubernetes: {"__env__": "base"}}
@contextmanager
def mock_func(self, func_name, return_value, test=False):
"""
Mock any of the kubernetes state function return values and set
the test options.
"""
name = "kubernetes.{}".format(func_name)
mocked = {name: MagicMock(return_value=return_value)}
with patch.dict(kubernetes.__salt__, mocked) as patched:
with patch.dict(kubernetes.__opts__, {"test": test}):
yield patched
def make_configmap(self, name, namespace="default", data=None):
return self.make_ret_dict(
kind="ConfigMap",
name=name,
namespace=namespace,
data=data,
)
def make_secret(self, name, namespace="default", data=None):
secret_data = self.make_ret_dict(
kind="Secret",
name=name,
namespace=namespace,
data=data,
)
# Base64 all of the values just like kubectl does
for key, value in secret_data["data"].items():
secret_data["data"][key] = base64.b64encode(
salt.utils.stringutils.to_bytes(value)
)
return secret_data
def make_node_labels(self, name="minikube"):
return {
"kubernetes.io/hostname": name,
"beta.kubernetes.io/os": "linux",
"beta.kubernetes.io/arch": "amd64",
"failure-domain.beta.kubernetes.io/region": "us-west-1",
}
def make_node(self, name="minikube"):
node_data = self.make_ret_dict(kind="Node", name="minikube")
node_data.update(
{
"api_version": "v1",
"kind": "Node",
"metadata": {
"annotations": {"node.alpha.kubernetes.io/ttl": "0"},
"labels": self.make_node_labels(name=name),
"name": name,
"namespace": None,
"self_link": "/api/v1/nodes/{name}".format(name=name),
"uid": "7811b8ae-c1a1-11e7-a55a-0800279fb61e",
},
"spec": {"external_id": name},
"status": {},
}
)
return node_data
def make_namespace(self, name="default"):
namespace_data = self.make_ret_dict(kind="Namespace", name=name)
del namespace_data["data"]
namespace_data.update(
{
"status": {"phase": "Active"},
"spec": {"finalizers": ["kubernetes"]},
"metadata": {
"name": name,
"namespace": None,
"labels": None,
"self_link": "/api/v1/namespaces/{namespace}".format(
namespace=name,
),
"annotations": None,
"uid": "752fceeb-c1a1-11e7-a55a-0800279fb61e",
},
}
)
return namespace_data
def make_ret_dict(self, kind, name, namespace=None, data=None):
"""
Make a minimal example configmap or secret for using in mocks
"""
assert kind in ("Secret", "ConfigMap", "Namespace", "Node")
if data is None:
data = {}
self_link = "/api/v1/namespaces/{namespace}/{kind}s/{name}".format(
namespace=namespace,
kind=kind.lower(),
name=name,
)
return_data = {
"kind": kind,
"data": data,
"api_version": "v1",
"metadata": {
"name": name,
"labels": None,
"namespace": namespace,
"self_link": self_link,
"annotations": {"kubernetes.io/change-cause": "salt-call state.apply"},
},
}
return return_data
def test_configmap_present__fail(self):
error = kubernetes.configmap_present(
name="testme",
data={1: 1},
source="salt://beyond/oblivion.jinja",
)
self.assertDictEqual(
{
"changes": {},
"result": False,
"name": "testme",
"comment": "'source' cannot be used in combination with 'data'",
},
error,
)
def test_configmap_present__create_test_true(self):
# Create a new configmap with test=True
with self.mock_func("show_configmap", return_value=None, test=True):
ret = kubernetes.configmap_present(
name="example",
data={"example.conf": "# empty config file"},
)
self.assertDictEqual(
{
"comment": "The configmap is going to be created",
"changes": {},
"name": "example",
"result": None,
},
ret,
)
def test_configmap_present__create(self):
# Create a new configmap
with self.mock_func("show_configmap", return_value=None):
cm = self.make_configmap(
name="test",
namespace="default",
data={"foo": "bar"},
)
with self.mock_func("create_configmap", return_value=cm):
actual = kubernetes.configmap_present(
name="test",
data={"foo": "bar"},
)
self.assertDictEqual(
{
"comment": "",
"changes": {"data": {"foo": "bar"}},
"name": "test",
"result": True,
},
actual,
)
def test_configmap_present__create_no_data(self):
# Create a new configmap with no 'data' attribute
with self.mock_func("show_configmap", return_value=None):
cm = self.make_configmap(
name="test",
namespace="default",
)
with self.mock_func("create_configmap", return_value=cm):
actual = kubernetes.configmap_present(name="test")
self.assertDictEqual(
{
"comment": "",
"changes": {"data": {}},
"name": "test",
"result": True,
},
actual,
)
def test_configmap_present__replace_test_true(self):
cm = self.make_configmap(
name="settings",
namespace="saltstack",
data={"foobar.conf": "# Example configuration"},
)
with self.mock_func("show_configmap", return_value=cm, test=True):
ret = kubernetes.configmap_present(
name="settings",
namespace="saltstack",
data={"foobar.conf": "# Example configuration"},
)
self.assertDictEqual(
{
"comment": "The configmap is going to be replaced",
"changes": {},
"name": "settings",
"result": None,
},
ret,
)
def test_configmap_present__replace(self):
cm = self.make_configmap(name="settings", data={"action": "make=war"})
# Replace an existing configmap
with self.mock_func("show_configmap", return_value=cm):
new_cm = cm.copy()
new_cm.update({"data": {"action": "make=peace"}})
with self.mock_func("replace_configmap", return_value=new_cm):
actual = kubernetes.configmap_present(
name="settings",
data={"action": "make=peace"},
)
self.assertDictEqual(
{
"comment": (
"The configmap is already present. Forcing recreation"
),
"changes": {"data": {"action": "make=peace"}},
"name": "settings",
"result": True,
},
actual,
)
def test_configmap_absent__noop_test_true(self):
# Nothing to delete with test=True
with self.mock_func("show_configmap", return_value=None, test=True):
actual = kubernetes.configmap_absent(name="NOT_FOUND")
self.assertDictEqual(
{
"comment": "The configmap does not exist",
"changes": {},
"name": "NOT_FOUND",
"result": None,
},
actual,
)
def test_configmap_absent__test_true(self):
# Configmap exists with test=True
cm = self.make_configmap(name="deleteme", namespace="default")
with self.mock_func("show_configmap", return_value=cm, test=True):
actual = kubernetes.configmap_absent(name="deleteme")
self.assertDictEqual(
{
"comment": "The configmap is going to be deleted",
"changes": {},
"name": "deleteme",
"result": None,
},
actual,
)
def test_configmap_absent__noop(self):
# Nothing to delete
with self.mock_func("show_configmap", return_value=None):
actual = kubernetes.configmap_absent(name="NOT_FOUND")
self.assertDictEqual(
{
"comment": "The configmap does not exist",
"changes": {},
"name": "NOT_FOUND",
"result": True,
},
actual,
)
def test_configmap_absent(self):
# Configmap exists, delete it!
cm = self.make_configmap(name="deleteme", namespace="default")
with self.mock_func("show_configmap", return_value=cm):
# The return from this module isn't used in the state
with self.mock_func("delete_configmap", return_value={}):
actual = kubernetes.configmap_absent(name="deleteme")
self.assertDictEqual(
{
"comment": "ConfigMap deleted",
"changes": {
"kubernetes.configmap": {
"new": "absent",
"old": "present",
},
},
"name": "deleteme",
"result": True,
},
actual,
)
def test_secret_present__fail(self):
actual = kubernetes.secret_present(
name="sekret",
data={"password": "monk3y"},
source="salt://nope.jinja",
)
self.assertDictEqual(
{
"changes": {},
"result": False,
"name": "sekret",
"comment": "'source' cannot be used in combination with 'data'",
},
actual,
)
def test_secret_present__exists_test_true(self):
secret = self.make_secret(name="sekret")
new_secret = secret.copy()
new_secret.update({"data": {"password": "uncle"}})
# Secret exists already and needs replacing with test=True
with self.mock_func("show_secret", return_value=secret):
with self.mock_func("replace_secret", return_value=new_secret, test=True):
actual = kubernetes.secret_present(
name="sekret",
data={"password": "uncle"},
)
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "sekret",
"comment": "The secret is going to be replaced",
},
actual,
)
def test_secret_present__exists(self):
# Secret exists and gets replaced
secret = self.make_secret(name="sekret", data={"password": "booyah"})
with self.mock_func("show_secret", return_value=secret):
with self.mock_func("replace_secret", return_value=secret):
actual = kubernetes.secret_present(
name="sekret",
data={"password": "booyah"},
)
self.assertDictEqual(
{
"changes": {"data": ["password"]},
"result": True,
"name": "sekret",
"comment": "The secret is already present. Forcing recreation",
},
actual,
)
def test_secret_present__create(self):
# Secret exists and gets replaced
secret = self.make_secret(name="sekret", data={"password": "booyah"})
with self.mock_func("show_secret", return_value=None):
with self.mock_func("create_secret", return_value=secret):
actual = kubernetes.secret_present(
name="sekret",
data={"password": "booyah"},
)
self.assertDictEqual(
{
"changes": {"data": ["password"]},
"result": True,
"name": "sekret",
"comment": "",
},
actual,
)
def test_secret_present__create_no_data(self):
# Secret exists and gets replaced
secret = self.make_secret(name="sekret")
with self.mock_func("show_secret", return_value=None):
with self.mock_func("create_secret", return_value=secret):
actual = kubernetes.secret_present(name="sekret")
self.assertDictEqual(
{
"changes": {"data": []},
"result": True,
"name": "sekret",
"comment": "",
},
actual,
)
def test_secret_present__create_test_true(self):
# Secret exists and gets replaced with test=True
secret = self.make_secret(name="sekret")
with self.mock_func("show_secret", return_value=None):
with self.mock_func("create_secret", return_value=secret, test=True):
actual = kubernetes.secret_present(name="sekret")
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "sekret",
"comment": "The secret is going to be created",
},
actual,
)
def test_secret_absent__noop_test_true(self):
with self.mock_func("show_secret", return_value=None, test=True):
actual = kubernetes.secret_absent(name="sekret")
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "sekret",
"comment": "The secret does not exist",
},
actual,
)
def test_secret_absent__noop(self):
with self.mock_func("show_secret", return_value=None):
actual = kubernetes.secret_absent(name="passwords")
self.assertDictEqual(
{
"changes": {},
"result": True,
"name": "passwords",
"comment": "The secret does not exist",
},
actual,
)
def test_secret_absent__delete_test_true(self):
secret = self.make_secret(name="credentials", data={"redis": "letmein"})
with self.mock_func("show_secret", return_value=secret):
with self.mock_func("delete_secret", return_value=secret, test=True):
actual = kubernetes.secret_absent(name="credentials")
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "credentials",
"comment": "The secret is going to be deleted",
},
actual,
)
def test_secret_absent__delete(self):
secret = self.make_secret(name="foobar", data={"redis": "letmein"})
deleted = {
"status": None,
"kind": "Secret",
"code": None,
"reason": None,
"details": None,
"message": None,
"api_version": "v1",
"metadata": {
"self_link": "/api/v1/namespaces/default/secrets/foobar",
"resource_version": "30292",
},
}
with self.mock_func("show_secret", return_value=secret):
with self.mock_func("delete_secret", return_value=deleted):
actual = kubernetes.secret_absent(name="foobar")
self.assertDictEqual(
{
"changes": {
"kubernetes.secret": {"new": "absent", "old": "present"},
},
"result": True,
"name": "foobar",
"comment": "Secret deleted",
},
actual,
)
def test_node_label_present__add_test_true(self):
labels = self.make_node_labels()
with self.mock_func("node_labels", return_value=labels, test=True):
actual = kubernetes.node_label_present(
name="com.zoo-animal",
node="minikube",
value="monkey",
)
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "com.zoo-animal",
"comment": "The label is going to be set",
},
actual,
)
def test_node_label_present__add(self):
node_data = self.make_node()
# Remove some of the defaults to make it simpler
node_data["metadata"]["labels"] = {
"beta.kubernetes.io/os": "linux",
}
labels = node_data["metadata"]["labels"]
with self.mock_func("node_labels", return_value=labels):
with self.mock_func("node_add_label", return_value=node_data):
actual = kubernetes.node_label_present(
name="failure-domain.beta.kubernetes.io/zone",
node="minikube",
value="us-central1-a",
)
self.assertDictEqual(
{
"comment": "",
"changes": {
"minikube.failure-domain.beta.kubernetes.io/zone": {
"new": {
"failure-domain.beta.kubernetes.io/zone": (
"us-central1-a"
),
"beta.kubernetes.io/os": "linux",
},
"old": {"beta.kubernetes.io/os": "linux"},
},
},
"name": "failure-domain.beta.kubernetes.io/zone",
"result": True,
},
actual,
)
def test_node_label_present__already_set(self):
node_data = self.make_node()
labels = node_data["metadata"]["labels"]
with self.mock_func("node_labels", return_value=labels):
with self.mock_func("node_add_label", return_value=node_data):
actual = kubernetes.node_label_present(
name="failure-domain.beta.kubernetes.io/region",
node="minikube",
value="us-west-1",
)
self.assertDictEqual(
{
"changes": {},
"result": True,
"name": "failure-domain.beta.kubernetes.io/region",
"comment": (
"The label is already set and has the specified value"
),
},
actual,
)
def test_node_label_present__update_test_true(self):
node_data = self.make_node()
labels = node_data["metadata"]["labels"]
with self.mock_func("node_labels", return_value=labels):
with self.mock_func("node_add_label", return_value=node_data, test=True):
actual = kubernetes.node_label_present(
name="failure-domain.beta.kubernetes.io/region",
node="minikube",
value="us-east-1",
)
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "failure-domain.beta.kubernetes.io/region",
"comment": "The label is going to be updated",
},
actual,
)
def test_node_label_present__update(self):
node_data = self.make_node()
# Remove some of the defaults to make it simpler
node_data["metadata"]["labels"] = {
"failure-domain.beta.kubernetes.io/region": "us-west-1",
}
labels = node_data["metadata"]["labels"]
with self.mock_func("node_labels", return_value=labels):
with self.mock_func("node_add_label", return_value=node_data):
actual = kubernetes.node_label_present(
name="failure-domain.beta.kubernetes.io/region",
node="minikube",
value="us-east-1",
)
self.assertDictEqual(
{
"changes": {
"minikube.failure-domain.beta.kubernetes.io/region": {
"new": {
"failure-domain.beta.kubernetes.io/region": (
"us-east-1"
)
},
"old": {
"failure-domain.beta.kubernetes.io/region": (
"us-west-1"
)
},
}
},
"result": True,
"name": "failure-domain.beta.kubernetes.io/region",
"comment": "The label is already set, changing the value",
},
actual,
)
def test_node_label_absent__noop_test_true(self):
labels = self.make_node_labels()
with self.mock_func("node_labels", return_value=labels, test=True):
actual = kubernetes.node_label_absent(
name="non-existent-label",
node="minikube",
)
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "non-existent-label",
"comment": "The label does not exist",
},
actual,
)
def test_node_label_absent__noop(self):
labels = self.make_node_labels()
with self.mock_func("node_labels", return_value=labels):
actual = kubernetes.node_label_absent(
name="non-existent-label",
node="minikube",
)
self.assertDictEqual(
{
"changes": {},
"result": True,
"name": "non-existent-label",
"comment": "The label does not exist",
},
actual,
)
def test_node_label_absent__delete_test_true(self):
labels = self.make_node_labels()
with self.mock_func("node_labels", return_value=labels, test=True):
actual = kubernetes.node_label_absent(
name="failure-domain.beta.kubernetes.io/region",
node="minikube",
)
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "failure-domain.beta.kubernetes.io/region",
"comment": "The label is going to be deleted",
},
actual,
)
def test_node_label_absent__delete(self):
node_data = self.make_node()
labels = node_data["metadata"]["labels"].copy()
node_data["metadata"]["labels"].pop("failure-domain.beta.kubernetes.io/region")
with self.mock_func("node_labels", return_value=labels):
with self.mock_func("node_remove_label", return_value=node_data):
actual = kubernetes.node_label_absent(
name="failure-domain.beta.kubernetes.io/region",
node="minikube",
)
self.assertDictEqual(
{
"result": True,
"changes": {
"kubernetes.node_label": {
"new": "absent",
"old": "present",
}
},
"comment": "Label removed from node",
"name": "failure-domain.beta.kubernetes.io/region",
},
actual,
)
def test_namespace_present__create_test_true(self):
with self.mock_func("show_namespace", return_value=None, test=True):
actual = kubernetes.namespace_present(name="saltstack")
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "saltstack",
"comment": "The namespace is going to be created",
},
actual,
)
def test_namespace_present__create(self):
namespace_data = self.make_namespace(name="saltstack")
with self.mock_func("show_namespace", return_value=None):
with self.mock_func("create_namespace", return_value=namespace_data):
actual = kubernetes.namespace_present(name="saltstack")
self.assertDictEqual(
{
"changes": {"namespace": {"new": namespace_data, "old": {}}},
"result": True,
"name": "saltstack",
"comment": "",
},
actual,
)
def test_namespace_present__noop_test_true(self):
namespace_data = self.make_namespace(name="saltstack")
with self.mock_func("show_namespace", return_value=namespace_data, test=True):
actual = kubernetes.namespace_present(name="saltstack")
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "saltstack",
"comment": "The namespace already exists",
},
actual,
)
def test_namespace_present__noop(self):
namespace_data = self.make_namespace(name="saltstack")
with self.mock_func("show_namespace", return_value=namespace_data):
actual = kubernetes.namespace_present(name="saltstack")
self.assertDictEqual(
{
"changes": {},
"result": True,
"name": "saltstack",
"comment": "The namespace already exists",
},
actual,
)
def test_namespace_absent__noop_test_true(self):
with self.mock_func("show_namespace", return_value=None, test=True):
actual = kubernetes.namespace_absent(name="salt")
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "salt",
"comment": "The namespace does not exist",
},
actual,
)
def test_namespace_absent__noop(self):
with self.mock_func("show_namespace", return_value=None):
actual = kubernetes.namespace_absent(name="salt")
self.assertDictEqual(
{
"changes": {},
"result": True,
"name": "salt",
"comment": "The namespace does not exist",
},
actual,
)
def test_namespace_absent__delete_test_true(self):
namespace_data = self.make_namespace(name="salt")
with self.mock_func("show_namespace", return_value=namespace_data, test=True):
actual = kubernetes.namespace_absent(name="salt")
self.assertDictEqual(
{
"changes": {},
"result": None,
"name": "salt",
"comment": "The namespace is going to be deleted",
},
actual,
)
def test_namespace_absent__delete_code_200(self):
namespace_data = self.make_namespace(name="salt")
deleted = namespace_data.copy()
deleted["code"] = 200
deleted.update({"code": 200, "message": None})
with self.mock_func("show_namespace", return_value=namespace_data):
with self.mock_func("delete_namespace", return_value=deleted):
actual = kubernetes.namespace_absent(name="salt")
self.assertDictEqual(
{
"changes": {
"kubernetes.namespace": {"new": "absent", "old": "present"}
},
"result": True,
"name": "salt",
"comment": "Terminating",
},
actual,
)
def test_namespace_absent__delete_status_terminating(self):
namespace_data = self.make_namespace(name="salt")
deleted = namespace_data.copy()
deleted.update(
{
"code": None,
"status": "Terminating namespace",
"message": "Terminating this shizzzle yo",
}
)
with self.mock_func("show_namespace", return_value=namespace_data):
with self.mock_func("delete_namespace", return_value=deleted):
actual = kubernetes.namespace_absent(name="salt")
self.assertDictEqual(
{
"changes": {
"kubernetes.namespace": {"new": "absent", "old": "present"}
},
"result": True,
"name": "salt",
"comment": "Terminating this shizzzle yo",
},
actual,
)
def test_namespace_absent__delete_status_phase_terminating(self):
# This is what kubernetes 1.8.0 looks like when deleting namespaces
namespace_data = self.make_namespace(name="salt")
deleted = namespace_data.copy()
deleted.update(
{"code": None, "message": None, "status": {"phase": "Terminating"}}
)
with self.mock_func("show_namespace", return_value=namespace_data):
with self.mock_func("delete_namespace", return_value=deleted):
actual = kubernetes.namespace_absent(name="salt")
self.assertDictEqual(
{
"changes": {
"kubernetes.namespace": {"new": "absent", "old": "present"}
},
"result": True,
"name": "salt",
"comment": "Terminating",
},
actual,
)
def test_namespace_absent__delete_error(self):
namespace_data = self.make_namespace(name="salt")
deleted = namespace_data.copy()
deleted.update({"code": 418, "message": "I' a teapot!", "status": None})
with self.mock_func("show_namespace", return_value=namespace_data):
with self.mock_func("delete_namespace", return_value=deleted):
actual = kubernetes.namespace_absent(name="salt")
self.assertDictEqual(
{
"changes": {},
"result": False,
"name": "salt",
"comment": "Something went wrong, response: {}".format(
deleted,
),
},
actual,
)