Merge "Update system:node clusterrolebinding for new host"

This commit is contained in:
Zuul 2024-03-07 00:04:53 +00:00 committed by Gerrit Code Review
commit eb27f7bbf3
4 changed files with 302 additions and 0 deletions

View File

@ -374,6 +374,7 @@ class KubeOperator(object):
self._kube_client_policy = None self._kube_client_policy = None
self._kube_client_custom_objects = None self._kube_client_custom_objects = None
self._kube_client_admission_registration = None self._kube_client_admission_registration = None
self._kube_client_rbac_authorization = None
def _load_kube_config(self): def _load_kube_config(self):
if not is_k8s_configured(): if not is_k8s_configured():
@ -422,6 +423,12 @@ class KubeOperator(object):
self._kube_client_admission_registration = client.AdmissionregistrationV1Api() self._kube_client_admission_registration = client.AdmissionregistrationV1Api()
return self._kube_client_admission_registration return self._kube_client_admission_registration
def _get_kubernetesclient_rbac_authorization(self):
if not self._kube_client_rbac_authorization:
self._load_kube_config()
self._kube_client_rbac_authorization = client.RbacAuthorizationV1Api()
return self._kube_client_rbac_authorization
def _retry_on_urllibs3_MaxRetryError(ex): # pylint: disable=no-self-argument def _retry_on_urllibs3_MaxRetryError(ex): # pylint: disable=no-self-argument
if isinstance(ex, MaxRetryError): if isinstance(ex, MaxRetryError):
LOG.warn('Retrying against MaxRetryError: {}'.format(ex)) LOG.warn('Retrying against MaxRetryError: {}'.format(ex))
@ -1554,3 +1561,30 @@ class KubeOperator(object):
except Exception as e: except Exception as e:
LOG.exception("Failed to fetch PodSecurityPolicies: %s" % e) LOG.exception("Failed to fetch PodSecurityPolicies: %s" % e)
raise raise
def kube_read_clusterrolebinding(self, name):
"""read a clusterrolebinding with data
"""
try:
rbac_authorization = self._get_kubernetesclient_rbac_authorization()
v1_cluster_role_binding_object = rbac_authorization.read_cluster_role_binding(name)
LOG.info("Clusterrolebinding %s retrieved successfully." % name)
return v1_cluster_role_binding_object
except Exception as ex:
LOG.exception("Failed to read clusterolebinding %s : %s" % (name, ex))
raise
def kube_patch_clusterrolebinding(self, name, body):
"""patch a clusterrolebinding with data
"""
try:
rbac_authorization = self._get_kubernetesclient_rbac_authorization()
v1_cluster_role_binding_object = \
rbac_authorization.patch_cluster_role_binding(name, body)
LOG.info("Clusterrolebinding %s updated successfully. "
"Updated object: %s" % (name, v1_cluster_role_binding_object))
except Exception as ex:
LOG.exception("Failed to patch clusterolebinding %s : %s" % (name, ex))
raise

View File

@ -55,6 +55,7 @@ from datetime import datetime
from datetime import timedelta from datetime import timedelta
from distutils.version import LooseVersion from distutils.version import LooseVersion
from copy import deepcopy from copy import deepcopy
from urllib3.exceptions import MaxRetryError
import tsconfig.tsconfig as tsc import tsconfig.tsconfig as tsc
from collections import namedtuple from collections import namedtuple
@ -1113,6 +1114,70 @@ class ConductorManager(service.PeriodicService):
return host return host
return None return None
def _retry_on_patch_system_node_clusterrolebinding(ex): # pylint: disable=no-self-argument
if isinstance(ex, MaxRetryError):
LOG.warning("system:node clusterrolebinding patch unsuccessful. Retrying...")
return True
else:
return False
@retry(stop_max_attempt_number=4,
wait_fixed=15 * 1000,
retry_on_exception=_retry_on_patch_system_node_clusterrolebinding)
def _system_node_clusterrolebinding_add_host(self, hostname):
"""Adds new host to the system:node clusterrolebinding
This method adds an entry of the new host as a subject to the
system:node clusterrolebinding.
:param hostname: name of the host to be added
"""
try:
subject = {
'api_group': 'rbac.authorization.k8s.io',
'kind': 'User',
'name': 'system:node:%s' % hostname,
'namespace': None
}
v1_cluster_role_binding_object = self._kube.kube_read_clusterrolebinding("system:node")
# As this code is also run during upgrade-activate operation,
# we must ensure that it does not create multiple entries for the same host
# if the upgrade-activate operation is re-run.
if not any(subject.name == ["system:node:%s" % hostname]
for subject in v1_cluster_role_binding_object.subjects):
v1_cluster_role_binding_object.subjects.append(subject)
self._kube.kube_patch_clusterrolebinding("system:node", v1_cluster_role_binding_object)
LOG.info("Host system:node:%s was added as a subject to the 'system:node' "
"clusterrolebinding" % hostname)
except Exception as ex:
LOG.error("Failed to add host system:node:%s as a subject to the 'system:node' "
"clusterrolebinding with error: %s" % (hostname, ex))
raise
@retry(stop_max_attempt_number=4,
wait_fixed=15 * 1000,
retry_on_exception=_retry_on_patch_system_node_clusterrolebinding)
def _system_node_clusterrolebinding_remove_host(self, hostname):
"""Remove host from the system:node clusterrolebinding
This method removes host entry from the subjects list in the
system:node clusterrolebinding.
:param hostname: name of the host to be removed
"""
try:
v1_cluster_role_binding_object = self._kube.kube_read_clusterrolebinding("system:node")
subjects = v1_cluster_role_binding_object.subjects
subjects[:] = [subject for subject in subjects
if subject.name != 'system:node:%s' % hostname]
self._kube.kube_patch_clusterrolebinding("system:node", v1_cluster_role_binding_object)
LOG.info("Host system:node:%s was removed from subjects in the 'system:node' "
"clusterrolebinding" % hostname)
except Exception as ex:
LOG.error("Failed to remove host system:node:%s from subjects in the 'system:node' "
"clusterrolebinding with error: %s" % (hostname, ex))
raise
def create_ihost(self, context, values, reason=None): def create_ihost(self, context, values, reason=None):
"""Create an ihost with the supplied data. """Create an ihost with the supplied data.
@ -1165,6 +1230,20 @@ class ConductorManager(service.PeriodicService):
ihost = self.dbapi.ihost_create(values, software_load=software_load) ihost = self.dbapi.ihost_create(values, software_load=software_load)
try:
hostname = values.get("hostname")
# As storage hosts don't run kubelet, we do not add them to the
# clusterrolebinding. Also, as kubernetes is not up while
# adding controller-0 during ansible bootstrap, we skip calling
# this method for controller-0 which is handled in the ansible
# code.
if hostname and \
not os.path.isfile(constants.ANSIBLE_BOOTSTRAP_FLAG) and \
values.get('personality') != constants.STORAGE:
self._system_node_clusterrolebinding_add_host(hostname)
except Exception as ex:
LOG.error("Error adding host to the system:node clusterrolebinding: %s" % ex)
# A host is being created, generate discovery log. # A host is being created, generate discovery log.
self._log_host_create(ihost, reason) self._log_host_create(ihost, reason)
@ -2873,6 +2952,16 @@ class ConductorManager(service.PeriodicService):
# allow a host with no personality to be unconfigured # allow a host with no personality to be unconfigured
pass pass
try:
# Storage hosts don't run kubelet and are not added to the
# clusterrolebinding.
if ihost_obj.personality != constants.STORAGE:
self._system_node_clusterrolebinding_remove_host(ihost_obj.hostname)
except Exception as ex:
# As this is just a cleanup operation we do nothing than just
# logging the error.
LOG.error("Error removing host from the clusterrolebinding: %s" % ex)
def _update_dependent_interfaces(self, interface, ihost, def _update_dependent_interfaces(self, interface, ihost,
phy_intf, oldmac, newmac, depth=1): phy_intf, oldmac, newmac, depth=1):
""" Updates the MAC address for dependent logical interfaces. """ Updates the MAC address for dependent logical interfaces.

View File

@ -767,6 +767,24 @@ class TestKubeOperator(base.TestCase):
'items': [] 'items': []
} }
self.read_clusterrolebinding_result = kubernetes.client.V1ClusterRoleBinding(
api_version="rbac.authorization.k8s.io/v1",
kind="ClusterRoleBinding",
metadata=kubernetes.client.V1ObjectMeta(
name="test_system:test_node",
),
role_ref=kubernetes.client.V1RoleRef(
api_group='rbac.authorization.k8s.io',
kind='ClusterRole',
name='test_system:test_node'
),
subjects=[kubernetes.client.V1Subject(
kind='User',
name='test_system:test_node:test_hostname',
api_group='rbac.authorization.k8s.io',
)],
)
def setUp(self): def setUp(self):
super(TestKubeOperator, self).setUp() super(TestKubeOperator, self).setUp()
@ -841,6 +859,13 @@ class TestKubeOperator(base.TestCase):
mock_list_pod_security_policy) mock_list_pod_security_policy)
self.mocked_list_pod_security_policy.start() self.mocked_list_pod_security_policy.start()
def mock_read_clusterrolebinding(obj, name):
return self.read_clusterrolebinding_result
self.mocked_read_clusterrolebinding = mock.patch(
'kubernetes.client.RbacAuthorizationV1Api.read_cluster_role_binding',
mock_read_clusterrolebinding)
self.mocked_read_clusterrolebinding.start()
self.kube_operator = kube.KubeOperator() self.kube_operator = kube.KubeOperator()
def tearDown(self): def tearDown(self):
@ -853,6 +878,7 @@ class TestKubeOperator(base.TestCase):
self.mocked_read_namespaced_service_account.stop() self.mocked_read_namespaced_service_account.stop()
self.mocked_read_namespaced_secret.stop() self.mocked_read_namespaced_secret.stop()
self.mocked_list_pod_security_policy.stop() self.mocked_list_pod_security_policy.stop()
self.mocked_read_clusterrolebinding.stop()
def test_kube_get_image_by_pod_name(self): def test_kube_get_image_by_pod_name(self):
@ -1335,6 +1361,58 @@ class TestKubeOperator(base.TestCase):
mock_kube_patch_config_map.assert_called_with( mock_kube_patch_config_map.assert_called_with(
'kubeadm-config', 'kube-system', patch_config_map_arg) 'kubeadm-config', 'kube-system', patch_config_map_arg)
def test_read_clusterrolebinding_success(self):
mock_read_clusterrolebinding = mock.MagicMock()
mocked_read_clusterrolebinding = mock.patch(
'kubernetes.client.RbacAuthorizationV1Api.read_cluster_role_binding',
mock_read_clusterrolebinding
)
mocked_read_clusterrolebinding.start().return_value = self.read_clusterrolebinding_result
self.addCleanup(mocked_read_clusterrolebinding.stop)
fake_clusterrole_binding_name = "test_system:test_node"
result = self.kube_operator.kube_read_clusterrolebinding(fake_clusterrole_binding_name)
mock_read_clusterrolebinding.assert_called_with(fake_clusterrole_binding_name)
self.assertEqual(result, self.read_clusterrolebinding_result)
def test_read_clusterrolebinding_exception(self):
mock_read_clusterrolebinding = mock.MagicMock()
mocked_read_clusterrolebinding = mock.patch(
'kubernetes.client.RbacAuthorizationV1Api.read_cluster_role_binding',
mock_read_clusterrolebinding
)
mocked_read_clusterrolebinding.start().side_effect = Exception("Fake Error")
self.addCleanup(mocked_read_clusterrolebinding.stop)
self.assertRaises( # noqa: H202
Exception,
self.kube_operator.kube_read_clusterrolebinding,
)
def test_patch_clusterrolebinding_success(self):
mock_patch_clusterrolebinding = mock.MagicMock()
mocked_patch_clusterrolebinding = mock.patch(
'kubernetes.client.RbacAuthorizationV1Api.patch_cluster_role_binding',
mock_patch_clusterrolebinding
)
mocked_patch_clusterrolebinding.start().return_value = self.read_clusterrolebinding_result
self.addCleanup(mocked_patch_clusterrolebinding.stop)
fake_clusterrole_binding_name = "test_system:test_node"
self.kube_operator.kube_patch_clusterrolebinding(fake_clusterrole_binding_name, {})
mock_patch_clusterrolebinding.assert_called_with(fake_clusterrole_binding_name, {})
def test_patch_clusterrolebinding_exception(self):
mock_patch_clusterrolebinding = mock.MagicMock()
mocked_patch_clusterrolebinding = mock.patch(
'kubernetes.client.RbacAuthorizationV1Api.patch_cluster_role_binding',
mock_patch_clusterrolebinding
)
mocked_patch_clusterrolebinding.start().side_effect = Exception("Fake Error")
self.addCleanup(mocked_patch_clusterrolebinding.stop)
self.assertRaises( # noqa: H202
Exception,
self.kube_operator.kube_patch_clusterrolebinding,
)
class TestKubernetesUtilities(base.TestCase): class TestKubernetesUtilities(base.TestCase):
def test_is_kube_version_supported(self): def test_is_kube_version_supported(self):

View File

@ -488,6 +488,40 @@ class ManagerTestCase(base.DbTestCase):
self.mocked_get_kube_versions.start() self.mocked_get_kube_versions.start()
self.addCleanup(self.mocked_get_kube_versions.stop) self.addCleanup(self.mocked_get_kube_versions.stop)
self.kube_read_clusterrolebinding_result = kubernetes.client.V1ClusterRoleBinding(
api_version="rbac.authorization.k8s.io/v1",
kind="ClusterRoleBinding",
metadata=kubernetes.client.V1ObjectMeta(
name="test_system:test_node",
),
role_ref=kubernetes.client.V1RoleRef(
api_group='rbac.authorization.k8s.io',
kind='ClusterRole',
name='test_system:test_node'
),
subjects=[kubernetes.client.V1Subject(
kind='User',
name='test_system:test_node:test_hostname',
api_group='rbac.authorization.k8s.io',
)],
)
mock_kube_read_clusterrolebinding = mock.MagicMock()
self.mocked_kube_read_clusterrolebinding = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_read_clusterrolebinding',
mock_kube_read_clusterrolebinding)
self.mocked_kube_read_clusterrolebinding.start().return_value = self.kube_read_clusterrolebinding_result
self.addCleanup(self.mocked_kube_read_clusterrolebinding.stop)
self.mock_kube_read_clusterrolebinding = mock_kube_read_clusterrolebinding
mock_kube_patch_clusterrolebinding = mock.MagicMock()
self.mocked_kube_patch_clusterrolebinding = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_patch_clusterrolebinding',
mock_kube_patch_clusterrolebinding)
self.mocked_kube_patch_clusterrolebinding.start().return_value = self.kube_read_clusterrolebinding_result
self.addCleanup(self.mocked_kube_patch_clusterrolebinding.stop)
self.mock_kube_patch_clusterrolebinding = mock_kube_patch_clusterrolebinding
self.service._puppet = mock.Mock() self.service._puppet = mock.Mock()
self.service._allocate_addresses_for_host = mock.Mock() self.service._allocate_addresses_for_host = mock.Mock()
self.service._update_pxe_config = mock.Mock() self.service._update_pxe_config = mock.Mock()
@ -611,6 +645,73 @@ class ManagerTestCase(base.DbTestCase):
for k, v in ihost_dict.items(): for k, v in ihost_dict.items():
self.assertEqual(res[k], v) self.assertEqual(res[k], v)
def test_system_node_clusterrolebinding_add_host_success(self):
self.service.start()
self.service._system_node_clusterrolebinding_add_host(
"test_system:test_node:test_hostname-1")
self.mock_kube_read_clusterrolebinding.assert_called()
self.mock_kube_patch_clusterrolebinding.assert_called()
def test_system_node_clusterrolebinding_add_host_already_exists(self):
mock_kube_read_clusterrolebinding = mock.MagicMock()
mocked_kube_read_clusterrolebinding = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_read_clusterrolebinding',
mock_kube_read_clusterrolebinding
)
mocked_kube_read_clusterrolebinding.start().return_value = \
self.kube_read_clusterrolebinding_result
self.addCleanup(mocked_kube_read_clusterrolebinding.stop)
mock_kube_patch_clusterrolebinding = mock.MagicMock()
mocked_kube_patch_clusterrolebinding = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_patch_clusterrolebinding',
mock_kube_patch_clusterrolebinding
)
mocked_kube_patch_clusterrolebinding.start()
self.addCleanup(mocked_kube_patch_clusterrolebinding.stop)
self.service.start()
self.service._system_node_clusterrolebinding_add_host(
"test_system:test_node:test_hostname")
mock_kube_read_clusterrolebinding.assert_called()
mock_kube_patch_clusterrolebinding.assert_called_with(
"system:node", self.kube_read_clusterrolebinding_result)
def test_system_node_clusterrolebinding_remove_host_success(self):
self.service.start()
self.service._system_node_clusterrolebinding_remove_host(
"test_system:test_node:test:hostname")
self.mock_kube_read_clusterrolebinding.assert_called()
self.mock_kube_patch_clusterrolebinding.assert_called()
def test_system_node_clusterrolebinding_add_host_exception(self):
mock_kube_read_clusterrolebinding = mock.MagicMock()
mocked_kube_read_clusterrolebinding = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_read_clusterrolebinding',
mock_kube_read_clusterrolebinding
)
mocked_kube_read_clusterrolebinding.start().side_effect = Exception("Fake Error")
self.addCleanup(mocked_kube_read_clusterrolebinding.stop)
self.service.start()
self.assertRaises( # noqa: H202
Exception,
self.service._system_node_clusterrolebinding_add_host,
)
def test_system_node_clusterrolebinding_remove_host_exception(self):
mock_kube_patch_clusterrolebinding = mock.MagicMock()
mocked_kube_patch_clusterrolebinding = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_patch_clusterrolebinding',
mock_kube_patch_clusterrolebinding
)
mocked_kube_patch_clusterrolebinding.start().side_effect = Exception("Fake Error")
self.addCleanup(mocked_kube_patch_clusterrolebinding.stop)
self.service.start()
self.assertRaises( # noqa: H202
Exception,
self.service._system_node_clusterrolebinding_remove_host,
)
def test_update_ihost(self): def test_update_ihost(self):
ihost = self._create_test_ihost() ihost = self._create_test_ihost()