kubernetes rootca pods update

API for pods rollout restart to update pods to receive certificates
signed by the new root CA. Respective to phases trustBothCAs
and trustNewCA
- kube rootca pods update phase trustBothCAs API and conductor
implementation.
- API unit tests for pods update phase trustBothCAs.
- kube rootca pods update phase trustNewCA API and conductor
implementation.
- API unit tests for pods update phase trustNewCA.

Story: 2008675
Task: 42703
Depends-on: https://review.opendev.org/c/starlingx/stx-puppet/+/798306
Change-Id: I34fe51dd1dca401864d165b04186ebe1fab178c7
Signed-off-by: Andy Ning <andy.ning@windriver.com>
This commit is contained in:
Andy Ning 2021-05-12 15:43:48 -04:00 committed by Joao Soubihe
parent a2e0edab01
commit 6eb997aefe
5 changed files with 278 additions and 1 deletions

View File

@ -183,11 +183,81 @@ class KubeRootCAHostUpdate(base.APIBase):
return kube_rootca_host_update
class KubeRootCAPodsUpdateController(rest.RestController):
def _precheck_trustbothcas(self, cluster_update):
# Pre checking if conditions met for phase trustBothCAs
if cluster_update.state not in \
[kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTBOTHCAS,
kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTBOTHCAS_FAILED]:
raise wsme.exc.ClientSideError(_(
"kube-rootca-pods-update phase trust-both-cas rejected: "
"not allowed when cluster update is in state: %s. "
"(only allowed when in state: %s or %s)"
% (cluster_update.state,
kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTBOTHCAS,
kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTBOTHCAS_FAILED)))
def _precheck_trustnewca(self, cluster_update):
# Pre checking if conditions met for phase trustNewCA
if cluster_update.state not in \
[kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTNEWCA,
kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTNEWCA_FAILED]:
raise wsme.exc.ClientSideError(_(
"kube-rootca-pods-update phase trust-new-ca rejected: "
"not allowed when cluster update is in state: %s. "
"(only allowed when in state: %s or %s)"
% (cluster_update.state,
kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTNEWCA,
kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTNEWCA_FAILED)))
@cutils.synchronized(LOCK_KUBE_ROOTCA_CONTROLLER)
@wsme_pecan.wsexpose(KubeRootCAUpdate, body=six.text_type)
def post(self, body):
# Check cluster update status
try:
update = pecan.request.dbapi.kube_rootca_update_get_one()
except exception.NotFound:
raise wsme.exc.ClientSideError(_(
"kube-rootca-pods-update rejected: No update in progress."))
phase = body['phase'].lower()
if phase == constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS:
# kube root CA update for pods phase trustBothCAs
self._precheck_trustbothcas(update)
update_state = kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTBOTHCAS
elif phase == constants.KUBE_CERT_UPDATE_TRUSTNEWCA:
# kube root CA update for pods phase trustNewCA
self._precheck_trustnewca(update)
update_state = kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTNEWCA
else:
raise wsme.exc.ClientSideError(_(
"kube-rootca-pods-update rejected: phase %s not supported."
% (phase)))
# Update the cluster update state
values = dict()
values['state'] = update_state
update = \
pecan.request.dbapi.kube_rootca_update_update(update.id, values)
# perform rpc to conductor to perform config apply
pecan.request.rpcapi.kube_certificate_update_for_pods(
pecan.request.context, phase)
return KubeRootCAUpdate.convert_with_links(update)
class KubeRootCAUpdateController(rest.RestController):
"""REST controller for kubernetes rootCA updates."""
# Controller for /kube_rootca_update/upload, upload new root CA
# certificate.
upload = KubeRootCAUploadController()
generate_cert = KubeRootCAGenerateController()
# Controller for /kube_rootca_update/pods, update pods certificates.
pods = KubeRootCAPodsUpdateController()
def __init__(self):
self.fm_api = fm_api.FaultAPIs()

View File

@ -7513,7 +7513,7 @@ class ConductorManager(service.PeriodicService):
LOG.error("No match for sysinv-agent manifest application reported! "
"reported_cfg: %(cfg)s status: %(status)s "
"iconfig: %(iconfig)s" % args)
# Kubernetes root CA update
# Kubernetes root CA host update
elif reported_cfg in [puppet_common.REPORT_KUBE_CERT_UPDATE_TRUSTBOTHCAS,
puppet_common.REPORT_KUBE_CERT_UPDATE_UPDATECERTS,
puppet_common.REPORT_KUBE_CERT_UPDATE_TRUSTNEWCA]:
@ -7531,6 +7531,23 @@ class ConductorManager(service.PeriodicService):
LOG.error("No match for sysinv-agent manifest application reported! "
"reported_cfg: %(cfg)s status: %(status)s "
"iconfig: %(iconfig)s" % args)
# Kubernetes root CA pods update
elif reported_cfg in \
[puppet_common.REPORT_KUBE_CERT_UPDATE_PODS_TRUSTBOTHCAS,
puppet_common.REPORT_KUBE_CERT_UPDATE_PODS_TRUSTNEWCA]:
if status == puppet_common.REPORT_SUCCESS:
# Update action was successful
success = True
self.report_kube_rootca_pods_update_success(reported_cfg)
elif status == puppet_common.REPORT_FAILURE:
# Update action has failed
self.report_kube_rootca_pods_update_failure(reported_cfg,
error)
else:
args = {'cfg': reported_cfg, 'status': status, 'iconfig': iconfig}
LOG.error("No match for sysinv-agent manifest application reported! "
"reported_cfg: %(cfg)s status: %(status)s "
"iconfig: %(iconfig)s" % args)
else:
LOG.error("Reported configuration '%(cfg)s' is not handled by"
" report_config_status! iconfig: %(iconfig)s" %
@ -8392,6 +8409,50 @@ class ConductorManager(service.PeriodicService):
c_update = self.dbapi.kube_rootca_update_get_one()
self.dbapi.kube_rootca_update_update(c_update.id, {'state': state})
def report_kube_rootca_pods_update_success(self, reported_cfg):
"""
Callback for Sysinv Agent on kube root CA pods update success
"""
LOG.info("Kube root CA update phase '%s' succeeded for pods"
% (reported_cfg))
if reported_cfg == \
puppet_common.REPORT_KUBE_CERT_UPDATE_PODS_TRUSTBOTHCAS:
state = kubernetes.KUBE_ROOTCA_UPDATED_PODS_TRUSTBOTHCAS
elif reported_cfg == \
puppet_common.REPORT_KUBE_CERT_UPDATE_PODS_TRUSTNEWCA:
state = kubernetes.KUBE_ROOTCA_UPDATED_PODS_TRUSTNEWCA
else:
LOG.info("Not supported reported_cfg: %s" % reported_cfg)
raise exception.SysinvException(_(
"Not supported reported_cfg: %s" % reported_cfg))
# Update cluster 'update state'
c_update = self.dbapi.kube_rootca_update_get_one()
self.dbapi.kube_rootca_update_update(c_update.id, {'state': state})
def report_kube_rootca_pods_update_failure(self, reported_cfg, error):
"""
Callback for Sysinv Agent on kube root CA pods update failure
"""
LOG.info("Kube root CA update phase '%s' failed for pods, error: %s"
% (reported_cfg, error))
if reported_cfg == \
puppet_common.REPORT_KUBE_CERT_UPDATE_PODS_TRUSTBOTHCAS:
state = kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTBOTHCAS_FAILED
elif reported_cfg == \
puppet_common.REPORT_KUBE_CERT_UPDATE_PODS_TRUSTNEWCA:
state = kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTNEWCA_FAILED
else:
LOG.info("Not supported reported_cfg: %s" % reported_cfg)
raise exception.SysinvException(_(
"Not supported reported_cfg: %s" % reported_cfg))
# Update cluster 'update state'
c_update = self.dbapi.kube_rootca_update_get_one()
self.dbapi.kube_rootca_update_update(c_update.id, {'state': state})
def create_controller_filesystems(self, context, rootfs_device):
""" Create the storage config based on disk size for database, platform,
extension, rabbit, etcd, docker-distribution, dc-vault(SC)
@ -14231,6 +14292,41 @@ class ConductorManager(service.PeriodicService):
config_uuid,
config_dict)
def kube_certificate_update_for_pods(self, context, phase):
"""Update the kube certificate for pods"""
# Updating pods' certificates is only needed to run once on active
# controller
host = self.dbapi.ihost_get(self.host_uuid)
config_uuid = self._config_update_hosts(context,
personalities=host.personality,
host_uuids=[host.uuid])
LOG.info("kube_certificate_update_for_pods config_uuid=%s"
% config_uuid)
phase = phase.lower()
if phase not in [constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS,
constants.KUBE_CERT_UPDATE_TRUSTNEWCA]:
raise exception.SysinvException(_(
"Invalid phase %s to update kube certificate for pods." %
phase))
puppet_class = [
'platform::kubernetes::master::rootca::pods::' + phase.replace('-', '') + '::runtime',
]
config_dict = {
"personalities": host.personality,
"classes": puppet_class,
"host_uuids": [host.uuid],
puppet_common.REPORT_STATUS_CFG: 'pods_' + phase,
}
self._config_apply_runtime_manifest(context,
config_uuid,
config_dict)
def device_image_state_sort_key(dev_img_state):
if dev_img_state.bitstream_type == dconstants.BITSTREAM_TYPE_ROOT_KEY:

View File

@ -2254,3 +2254,17 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
phase=phase
)
)
def kube_certificate_update_for_pods(self, context, phase):
"""
Asynchronously, have the conductor update certificates for pods.
:param context: request context.
:param phase: the phase of the update.
"""
return self.cast(
context, self.make_msg(
'kube_certificate_update_for_pods',
phase=phase
)
)

View File

@ -41,9 +41,15 @@ REPORT_PCI_SRIOV_CONFIG = 'pci_sriov_config'
REPORT_CEPH_OSD_CONFIG = 'ceph_osd'
REPORT_CEPH_RADOSGW_CONFIG = 'ceph_radosgw'
REPORT_CEPH_ROOK_CONFIG = 'ceph_rook_config'
# puppet report configs for hosts cert update
REPORT_KUBE_CERT_UPDATE_TRUSTBOTHCAS = constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS
REPORT_KUBE_CERT_UPDATE_UPDATECERTS = constants.KUBE_CERT_UPDATE_UPDATECERTS
REPORT_KUBE_CERT_UPDATE_TRUSTNEWCA = constants.KUBE_CERT_UPDATE_TRUSTNEWCA
# puppet report configs for pods cert update
REPORT_KUBE_CERT_UPDATE_PODS_TRUSTBOTHCAS = \
'pods_' + constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS
REPORT_KUBE_CERT_UPDATE_PODS_TRUSTNEWCA = \
'pods_' + constants.KUBE_CERT_UPDATE_TRUSTNEWCA
def puppet_apply_manifest(ip_address, personality,

View File

@ -67,6 +67,9 @@ class FakeConductorAPI(object):
def kube_certificate_update_by_host(self, context, host_uuid, phase):
return
def kube_certificate_update_for_pods(self, context, phase):
return
class TestKubeRootCAUpdate(base.FunctionalTest):
@ -292,6 +295,94 @@ class TestKubeRootCAGenerate(TestKubeRootCAUpdate,
self.assertFalse(resp.get('error'))
class TestKubeRootCAPodsUpdateTrustBothCAs(TestKubeRootCAUpdate,
dbbase.ProvisionedControllerHostTestCase):
def setUp(self):
super(TestKubeRootCAPodsUpdateTrustBothCAs, self).setUp()
self.phase = constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS
self.post_url = '/kube_rootca_update/pods'
self.headers = {'User-Agent': 'sysinv-test'}
def test_rootca_update_pods(self):
# Test kube root CA update for pods
create_dict = {'phase': self.phase}
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTBOTHCAS)
result = self.post_json(self.post_url, create_dict,
headers=self.headers)
# Verify that the rootca update pods has the expected attributes
self.assertEqual(result.json['state'],
kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTBOTHCAS)
def test_rootca_update_pods_reject_wrong_state(self):
# Test kube root CA update for pods - rejected, not in right state
create_dict = {'phase': self.phase}
# The cluster update state is in updating hosts
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
result = self.post_json(self.post_url, create_dict,
headers=self.headers,
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, result.status_int)
self.assertIn("kube-rootca-pods-update phase trust-both-cas rejected: "
"not allowed when cluster update is in state: %s. "
"(only allowed when in state: %s or %s)"
% (kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS,
kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTBOTHCAS,
kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTBOTHCAS_FAILED),
result.json['error_message'])
class TestKubeRootCAPodsUpdateTrustNewCA(TestKubeRootCAUpdate,
dbbase.ProvisionedControllerHostTestCase):
def setUp(self):
super(TestKubeRootCAPodsUpdateTrustNewCA, self).setUp()
self.phase = constants.KUBE_CERT_UPDATE_TRUSTNEWCA
self.post_url = '/kube_rootca_update/pods'
self.headers = {'User-Agent': 'sysinv-test'}
def test_rootca_update_pods(self):
# Test kube root CA update for pods
create_dict = {'phase': self.phase}
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTNEWCA)
result = self.post_json(self.post_url, create_dict,
headers=self.headers)
# Verify that the rootca update pods has the expected attributes
self.assertEqual(result.json['state'],
kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTNEWCA)
def test_rootca_update_pods_reject_wrong_state(self):
# Test kube root CA update for pods - rejected, not in right state
create_dict = {'phase': self.phase}
# The cluster update state is in updating hosts
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTBOTHCAS)
result = self.post_json(self.post_url, create_dict,
headers=self.headers,
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, result.status_int)
self.assertIn("kube-rootca-pods-update phase trust-new-ca rejected: "
"not allowed when cluster update is in state: %s. "
"(only allowed when in state: %s or %s)"
% (kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTBOTHCAS,
kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTNEWCA,
kubernetes.KUBE_ROOTCA_UPDATING_PODS_TRUSTNEWCA_FAILED),
result.json['error_message'])
class TestKubeRootCAHostUpdate(base.FunctionalTest):
# API_HEADERS are a generic header passed to most API calls
API_HEADERS = {'User-Agent': 'sysinv-test'}