kubernetes rootca host update trustBothCAs phase

This update added the basic of kubernetes rootca host
update phase trustBothCAs REST API.

- pre-checking before perform the operation
- Set sysinv-conductor and sysinv-agent to apply runtime
puppet manifest for new configuration of kubernetes components
on the host that was indicated through CLI (to be added
on further development)
- puppet plugin adjustment to apply the configuration
required in this phase
- state update after the operation
- tox unit tests

Story: 2008675
Task: 42407
Depends-on: https://review.opendev.org/c/starlingx/stx-puppet/+/788946
Change-Id: Ie75b63aad31f683fee89928e95b8b9c030c84d0b
Signed-off-by: Andy Ning <andy.ning@windriver.com>
This commit is contained in:
Andy Ning 2021-03-23 16:43:41 -04:00 committed by Joao Soubihe
parent d92bb6f98c
commit 33e24936d3
9 changed files with 709 additions and 5 deletions

View File

@ -58,6 +58,8 @@ from sysinv.api.controllers.v1 import collection
from sysinv.api.controllers.v1 import cpu as cpu_api
from sysinv.api.controllers.v1 import cpu_utils
from sysinv.api.controllers.v1 import disk
from sysinv.api.controllers.v1 \
import kube_rootca_update as kube_rootca_update_api
from sysinv.api.controllers.v1 import partition
from sysinv.api.controllers.v1 import ceph_mon
from sysinv.api.controllers.v1 import interface as interface_api
@ -506,6 +508,9 @@ class Host(base.APIBase):
host_fs = [link.Link]
"Links to the collection of host_fs on this ihost"
kube_update_ca = [link.Link]
"Links to the collection of kube_update_ca on this ihost"
isensors = [link.Link]
"Links to the collection of isensors on this ihost"
@ -752,6 +757,19 @@ class Host(base.APIBase):
bookmark=True)
]
uhost.kube_update_ca = [
link.Link.make_link('self',
pecan.request.host_url,
'ihosts',
uhost.uuid + "/kube_update_ca"),
link.Link.make_link(
'bookmark',
pecan.request.host_url,
'ihosts',
uhost.uuid + "/kube_update_ca",
bookmark=True)
]
uhost.isensors = [link.Link.make_link('self',
pecan.request.host_url,
'ihosts',
@ -1087,6 +1105,10 @@ class HostController(rest.RestController):
host_fs = host_fs_api.HostFsController(from_ihosts=True)
"Expose host_fs as a sub-element of ihosts"
kube_update_ca = \
kube_rootca_update_api.KubeRootCAHostUpdateController(from_ihosts=True)
"Expose kube_update_ca as a sub-element of ihosts"
addresses = address_api.AddressController(parent="ihosts")
"Expose addresses as a sub-element of ihosts"

View File

@ -25,12 +25,14 @@ from sysinv.common import constants
from sysinv.common import exception
from sysinv.common import kubernetes
from sysinv.common import utils as cutils
from sysinv._i18n import _
from wsme import types as wtypes
LOG = log.getLogger(__name__)
LOCK_KUBE_ROOTCA_UPLOAD_CONTROLLER = 'KubeRootCAUploadController'
LOCK_KUBE_ROOTCA_UPDATE_CONTROLLER = 'KubeRootCAUpdateController'
LOCK_KUBE_ROOTCA_HOST_UPDATE_CONTROLLER = 'KubeRootCAHostUpdateController'
class KubeRootCAUploadController(rest.RestController):
@ -112,6 +114,60 @@ class KubeRootCAUpdate(base.APIBase):
return kube_rootca_update
class KubeRootCAHostUpdate(base.APIBase):
"""API representation of a Kubernetes RootCA Host Update."""
id = int
"Unique ID for this entry"
uuid = types.uuid
"Unique UUID for this entry"
target_rootca_cert = wtypes.text
"The target certificate for the kubernetes rootCA host update"
effective_rootca_cert = wtypes.text
"The current certificate of the kubernetes rootCA on this host"
state = wtypes.text
"Kubernetes rootCA host update state"
capabilities = {wtypes.text: utils.ValidTypes(wtypes.text,
six.integer_types)}
"Additional properties to be used in kube_rootca_host_update operations"
links = [link.Link]
"A list containing a self link and associated kubernetes rootca host "
"update links"
def __init__(self, **kwargs):
self.fields = objects.kube_rootca_host_update.fields.keys()
for k in self.fields:
if not hasattr(self, k):
continue
setattr(self, k, kwargs.get(k, wtypes.Unset))
@classmethod
def convert_with_links(cls, kube_rootca_host_update, expand=True):
kube_rootca_host_update = KubeRootCAHostUpdate(
**kube_rootca_host_update.as_dict())
if not expand:
kube_rootca_host_update.unset_fields_except(['uuid',
'target_rootca_cert', 'effective_rootca_cert', 'state'])
kube_rootca_host_update.links = [
link.Link.make_link('self', pecan.request.host_url,
'kube_rootca_host_update',
kube_rootca_host_update.uuid),
link.Link.make_link('bookmark',
pecan.request.host_url,
'kube_rootca_host_update',
kube_rootca_host_update.uuid,
bookmark=True)
]
return kube_rootca_host_update
class KubeRootCAUpdateController(rest.RestController):
"""REST controller for kubernetes rootCA updates."""
@ -202,3 +258,156 @@ class KubeRootCAUpdateController(rest.RestController):
rpc_kube_rootca_update = objects.kube_rootca_update.get_by_uuid(
pecan.request.context, uuid)
return KubeRootCAUpdate.convert_with_links(rpc_kube_rootca_update)
class KubeRootCAHostUpdateController(rest.RestController):
"""REST controller for kube host root CA certificate."""
def __init__(self, from_ihosts=False):
self._from_ihosts = from_ihosts
def _precheck_trustbothcas(self, cluster_update, ihost):
""" Pre checking if conditions met for phase trust-both-cas """
# Get all the host update state
host_updates = pecan.request.dbapi.kube_rootca_host_update_get_list()
if len(host_updates) == 0:
# No hosts start update yet
if cluster_update.state not in \
[kubernetes.KUBE_ROOTCA_UPDATE_CERT_UPLOADED,
kubernetes.KUBE_ROOTCA_UPDATE_CERT_GENERATED]:
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: No new certificate "
"available"))
else:
# Not allowed if any host updates are in progress
for host_update in host_updates:
if host_update.state == \
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS:
host_name = pecan.request.dbapi.ihost_get(
host_update.host_id).hostname
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: update in progess "
"on host %s" % host_name))
# Check if this host update ever started
for host_update in host_updates:
if ihost.id == host_update.host_id:
update_ever_started = host_update
break
else:
update_ever_started = None
if update_ever_started is None:
# Update never started on this host.
# Allow start only if overall update state is correct.
if cluster_update.state not in \
[kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS]:
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: not "
"allowed when cluster update is in state: %s. "
"(only allowed when in state: %s)"
% (cluster_update.state,
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)))
else:
# Update ever started on this host.
if update_ever_started.state in \
[kubernetes.
KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS_FAILED]:
# Allowed retry only if update on this host ever failed
pass
elif update_ever_started.state in \
[kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTBOTHCAS]:
# Return error indicating update on this host already
# completed.
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: update already "
"completed on host %s" % ihost.hostname))
else:
# This could be the case where the cluster update already
# passes trust-both-cas (eg. in updateCerts phase), but
# client tries to make an update call of phase
# trust-both-cas.
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: not allowed when "
"cluster update is in state: %s. "
"(only allowed when in state: %s)"
% (cluster_update.state,
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)))
@cutils.synchronized(LOCK_KUBE_ROOTCA_HOST_UPDATE_CONTROLLER)
@wsme_pecan.wsexpose(KubeRootCAHostUpdate, types.uuid, body=six.text_type)
def post(self, host_uuid, body):
"""Update the kubernetes root CA certificate on this host"""
# Check cluster update status
try:
update = pecan.request.dbapi.kube_rootca_update_get_one()
except exception.NotFound:
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: No update in progress."))
# Check if the new root CA cert secret exists, in case the secret
# is deleted unexpectly.
kube_operator = kubernetes.KubeOperator()
try:
cert_secret = kube_operator.kube_get_secret(
constants.KUBE_ROOTCA_SECRET,
kubernetes.NAMESPACE_DEPLOYMENT,
)
except Exception:
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: failed to get new root CA "
"cert secret from kubernetes."))
if cert_secret is None:
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: no new root CA cert found."))
ihost = pecan.request.dbapi.ihost_get(host_uuid)
if body['phase'].lower() == constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS:
# kube root CA update on host phase trust-both-cas
self._precheck_trustbothcas(update, ihost)
update_state = kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS
else:
raise wsme.exc.ClientSideError(_(
"kube-rootca-host-update rejected: not supported phase."))
# Update the cluster update state
c_values = dict()
c_values['state'] = update_state
c_update = pecan.request.dbapi.kube_rootca_update_get_one()
pecan.request.dbapi.kube_rootca_update_update(c_update.id, c_values)
# Create or update "update state" on this host
h_values = dict()
h_values['state'] = update_state
h_values['effective_rootca_cert'] = c_update.from_rootca_cert
h_values['target_rootca_cert'] = c_update.to_rootca_cert
try:
h_update = pecan.request.dbapi.kube_rootca_host_update_get_by_host(
ihost.id)
h_update = pecan.request.dbapi.kube_rootca_host_update_update(
h_update.id, h_values)
except exception.NotFound:
h_update = pecan.request.dbapi.kube_rootca_host_update_create(
ihost.id, h_values)
phase = body['phase'].lower()
if phase not in [constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS,
constants.KUBE_CERT_UPDATE_UPDATECERTS,
constants.KUBE_CERT_UPDATE_TRUSTNEWCA]:
raise exception.SysinvException(_(
"Invalid phase %s to update kube certificate." %
phase))
# perform rpc to conductor to perform config apply
pecan.request.rpcapi.kube_certificate_update_by_host(
pecan.request.context, host_uuid, body['phase'])
LOG.info("Kubernetes rootca update started on host: %s"
% ihost.hostname)
return KubeRootCAHostUpdate.convert_with_links(h_update)

View File

@ -1892,6 +1892,7 @@ CERT_NAMESPACE_PLATFORM_CERTS = 'deployment'
CERT_MODE_TO_SECRET_NAME = {
CERT_MODE_SSL: RESTAPI_CERT_SECRET_NAME,
CERT_MODE_DOCKER_REGISTRY: REGISTRY_CERT_SECRET_NAME
}
# Storage associated networks

View File

@ -7527,6 +7527,24 @@ 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
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]:
host_uuid = iconfig['host_uuid']
if status == puppet_common.REPORT_SUCCESS:
# Update action was successful
success = True
self.report_kube_rootca_update_success(host_uuid, reported_cfg)
elif status == puppet_common.REPORT_FAILURE:
# Update action has failed
self.report_kube_rootca_update_failure(host_uuid, 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" %
@ -8317,6 +8335,77 @@ class ConductorManager(service.PeriodicService):
upgrade.uuid,
{'state': constants.UPGRADE_ACTIVATION_FAILED})
def report_kube_rootca_update_success(self, host_uuid, reported_cfg):
"""
Callback for Sysinv Agent on kube root CA update success
"""
LOG.info("Kube root CA update phase '%s' succeeded on host: %s"
% (reported_cfg, host_uuid))
if reported_cfg == puppet_common.REPORT_KUBE_CERT_UPDATE_TRUSTBOTHCAS:
state = kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTBOTHCAS
elif reported_cfg == puppet_common.REPORT_KUBE_CERT_UPDATE_UPDATECERTS:
state = kubernetes.KUBE_ROOTCA_UPDATED_HOST_UPDATECERTS
elif reported_cfg == puppet_common.REPORT_KUBE_CERT_UPDATE_TRUSTNEWCA:
state = kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTNEWCA
else:
LOG.info("Not supported reported_cfg: %s" % reported_cfg)
raise exception.SysinvException(_(
"Not supported reported_cfg: %s" % reported_cfg))
# Update host 'update state'
h_update = self.dbapi.kube_rootca_host_update_get_by_host(host_uuid)
self.dbapi.kube_rootca_host_update_update(h_update.id,
{'state': state})
# Update cluster 'update state'
hosts = self.dbapi.ihost_get_list()
h_updates = self.dbapi.kube_rootca_host_update_get_list()
# Look to see if there are other hosts not successfully updated yet
for host in hosts:
if host.uuid == host_uuid:
continue
for h_update in h_updates:
# This host has been updated successfully
if host.id == h_update.host_id and h_update.state == state:
break
else:
# This host has not been updated successfully
break
else:
# All other hosts has been updated successfully
c_update = self.dbapi.kube_rootca_update_get_one()
self.dbapi.kube_rootca_update_update(c_update.id, {'state': state})
def report_kube_rootca_update_failure(self, host_uuid, reported_cfg,
error):
"""
Callback for Sysinv Agent on kube root CA update failure
"""
LOG.info("Kube root CA update phase '%s' failed on host: %s, error: %s"
% (reported_cfg, host_uuid, error))
if reported_cfg == puppet_common.REPORT_KUBE_CERT_UPDATE_TRUSTBOTHCAS:
state = kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS_FAILED
elif reported_cfg == puppet_common.REPORT_KUBE_CERT_UPDATE_UPDATECERTS:
state = kubernetes.KUBE_ROOTCA_UPDATING_HOST_UPDATECERTS_FAILED
elif reported_cfg == puppet_common.REPORT_KUBE_CERT_UPDATE_TRUSTNEWCA:
state = kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTNEWCA_FAILED
else:
LOG.info("Not supported reported_cfg: %s" % reported_cfg)
raise exception.SysinvException(_(
"Not supported reported_cfg: %s" % reported_cfg))
# Update host 'update state'
h_update = self.dbapi.kube_rootca_host_update_get_by_host(host_uuid)
self.dbapi.kube_rootca_host_update_update(h_update.id,
{'state': state})
# 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)
@ -13927,6 +14016,47 @@ class ConductorManager(service.PeriodicService):
"".format(app.name, action, str(e)))
raise
def kube_certificate_update_by_host(self, context, host_uuid, phase):
"""Update the kube certificate for a host"""
try:
host = self.dbapi.ihost_get(host_uuid)
except exception.ServerNotFound:
# This really shouldn't happen.
LOG.exception("Invalid host_uuid %s" % host_uuid)
return
config_uuid = self._config_update_hosts(context,
personalities=host.personality,
host_uuids=[host.uuid])
LOG.info("kube_certificate_update_by_host config_uuid=%s"
% config_uuid)
if host.personality == constants.CONTROLLER:
puppet_class = [
'platform::kubernetes::master::rootca::' + phase.replace('-', '') + '::runtime',
]
elif host.personality == constants.WORKER:
puppet_class = [
'platform::kubernetes::worker::rootca::' + phase.replace('-', '') + '::runtime',
]
else:
raise exception.SysinvException(_(
"Invalid personality %s to update kube certificate." %
host.personality))
config_dict = {
"personalities": host.personality,
"classes": puppet_class,
"host_uuids": [host.uuid],
puppet_common.REPORT_STATUS_CFG: 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

@ -2229,3 +2229,19 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
"""
return self.call(context, self.make_msg('save_kubernetes_rootca_cert',
ca_file=certificate_file))
def kube_certificate_update_by_host(self, context, host_uuid, phase):
"""
Asynchronously, have the conductor update the host's kube certificates.
:param context: request context.
:param host_uuid: the host to update the certificate on.
:param phase: the phase of the update.
"""
return self.cast(
context, self.make_msg(
'kube_certificate_update_by_host',
host_uuid=host_uuid,
phase=phase
)
)

View File

@ -11,6 +11,7 @@ import os
from oslo_log import log as logging
from sysinv._i18n import _
from sysinv.common import constants
from sysinv.common import exception
from tsconfig import tsconfig
@ -40,6 +41,9 @@ 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'
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
def puppet_apply_manifest(ip_address, personality,

View File

@ -37,6 +37,10 @@ CERTIFICATE_KEY_USER = "certificate-key"
# kubeadm configuration option
KUBECONFIG = "--kubeconfig=%s" % kubernetes.KUBERNETES_ADMIN_CONF
# kubernetes root CA certificate params
KUBE_ROOTCA_CERT_NS = 'deployment'
KUBE_ROOTCA_CERT_SECRET = 'system-kube-rootca-certificate'
class KubernetesPuppet(base.BasePuppet):
"""Class to encapsulate puppet operations for kubernetes configuration"""
@ -119,6 +123,43 @@ class KubernetesPuppet(base.BasePuppet):
return config
def get_secure_system_config(self):
"""Update the hiera configuration secure data"""
config = {}
cert, key = self._get_kubernetes_rootca_cert_key()
config.update({
'platform::kubernetes::params::rootca_cert': cert,
'platform::kubernetes::params::rootca_key': key,
})
return config
@staticmethod
def _get_kubernetes_rootca_cert_key():
""""Get kubernetes root CA certficate secret from cert-manager"""
try:
kube_operator = kubernetes.KubeOperator()
secret = kube_operator.kube_get_secret(KUBE_ROOTCA_CERT_SECRET,
KUBE_ROOTCA_CERT_NS)
# The root CA cert/key are not stored in kubernetes yet
if not secret:
return 'undef', 'undef'
if hasattr(secret, 'data') and secret.data:
cert = secret.data.get('tls.crt', None)
key = secret.data.get('tls.key', None)
if cert and key:
return cert, key
raise Exception('Failed to get secret %s\\%s' % (
KUBE_ROOTCA_CERT_NS, KUBE_ROOTCA_CERT_SECRET))
except exception.KubeNotConfigured:
# During ansible bootstrap, kubernetes is not configured.
# Set the cert and key to 'undef'
return 'undef', 'undef'
@staticmethod
def _get_active_kubernetes_version():
"""Get the active kubernetes version

View File

@ -56,6 +56,9 @@ class FakeConductorAPI(object):
def setup_config_certificate(self, data):
self.config_certificate_return = data
def kube_certificate_update_by_host(self, context, host_uuid, phase):
return
class TestKubeRootCAUpdate(base.FunctionalTest):
@ -72,6 +75,20 @@ class TestKubeRootCAUpdate(base.FunctionalTest):
self.addCleanup(p.stop)
self.setup_health_mocked_calls()
self.setup_kubernetes_calls()
def setup_kubernetes_calls(self):
""" Mock KubeOperator calls invoked from methods in kube rootca update """
# mocking kube_create_secret
p = mock.patch('sysinv.common.kubernetes.KubeOperator.kube_create_secret')
self.mock_kube_create_secret = p.start()
self.addCleanup(p.stop)
# mocking apply_custom_resource
l = mock.patch('sysinv.common.kubernetes.KubeOperator.apply_custom_resource')
self.mock_conductor_api = l.start()
self.addCleanup(l.stop)
def setup_health_mocked_calls(self):
"""Mock away the API calls invoked from the health check.
@ -213,11 +230,7 @@ class TestKubeRootCAUpload(TestKubeRootCAUpdate,
super(TestKubeRootCAUpload, self).setUp()
self.fake_conductor_api.service.dbapi = self.dbapi
@mock.patch.object(kubernetes.KubeOperator,
'kube_create_secret')
@mock.patch.object(kubernetes.KubeOperator,
'apply_custom_resource')
def test_upload_rootca(self, mock_create_secret, mock_create_custom_resource):
def test_upload_rootca(self):
dbutils.create_test_kube_rootca_update(state=kubernetes.KUBE_ROOTCA_UPDATE_STARTED)
certfile = os.path.join(os.path.dirname(__file__), "data",
'rootca-with-key.pem')
@ -242,3 +255,270 @@ class TestKubeRootCAUpload(TestKubeRootCAUpdate,
self.assertTrue(resp.get('success'))
self.assertEqual(resp.get('success'), fake_save_rootca_return.get('success'))
self.assertFalse(resp.get('error'))
class TestKubeRootCAHostUpdate(base.FunctionalTest):
# API_HEADERS are a generic header passed to most API calls
API_HEADERS = {'User-Agent': 'sysinv-test'}
# API_PREFIX is the prefix for the URL
API_PREFIX = '/ihosts'
def setUp(self):
super(TestKubeRootCAHostUpdate, self).setUp()
# Mock the Conductor API
self.fake_conductor_api = FakeConductorAPI()
# rather than start the fake_conductor_api.service, we stage its dbapi
self.fake_conductor_api.service.dbapi = self.dbapi
p = mock.patch('sysinv.conductor.rpcapi.ConductorAPI')
self.mock_conductor_api = p.start()
self.mock_conductor_api.return_value = self.fake_conductor_api
self.addCleanup(p.stop)
# Mock kubeOperator kube_get_secret
mock_kube_get_secret = mock.MagicMock()
z = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_get_secret',
mock_kube_get_secret
)
self.mock_kube_get_secret = z.start()
self.addCleanup(z.stop)
self.headers = {'User-Agent': 'sysinv-test'}
self.post_url = \
'%s/%s/kube_update_ca' % (self.API_PREFIX, self.host.uuid)
def set_phase(self, phase):
self.phase = phase
class TestKubeRootCAHostUpdateTrustBothCAs(TestKubeRootCAHostUpdate,
dbbase.ProvisionedAIODuplexSystemTestCase):
def setUp(self):
super(TestKubeRootCAHostUpdateTrustBothCAs, self).setUp()
# Set host root CA update phase
self.set_phase(constants.KUBE_CERT_UPDATE_TRUSTBOTHCAS)
def test_create_from_uploaded_cert(self):
# Test creation of kubernetes rootca host update
create_dict = {'phase': self.phase}
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATE_CERT_UPLOADED)
result = self.post_json(self.post_url, create_dict,
headers=self.headers)
# Verify that the rootca host update has the expected attributes
self.assertEqual(result.json['state'],
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
# Verify that the overall rootca update has the expected attributes
result = dbutils.get_kube_rootca_update()
self.assertEqual(result.state,
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
def test_create_from_generated_cert(self):
# Test creation of kubernetes rootca host update
create_dict = {'phase': self.phase}
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATE_CERT_GENERATED)
result = self.post_json(self.post_url, create_dict,
headers=self.headers)
# Verify that the rootca host update has the expected attributes
self.assertEqual(result.json['state'],
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
# Verify that the overall rootca update has the expected attributes
result = dbutils.get_kube_rootca_update()
self.assertEqual(result.state,
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
def test_create_other_hosts_updated(self):
# Test creation of kubernetes rootca host update
# Allow update on this hosts when overall update in progress
create_dict = {'phase': self.phase}
# overall update in progress with some hosts updated
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
# root CA update on host2 completed
dbutils.create_test_kube_rootca_host_update(host_id=self.host2.id,
state=kubernetes.KUBE_ROOTCA_UPDATED_HOST_TRUSTBOTHCAS)
result = self.post_json(self.post_url, create_dict,
headers=self.headers)
# Verify that the rootca host update has the expected attributes
self.assertEqual(result.json['state'],
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
# Verify that the overall rootca update has the expected attributes
result = dbutils.get_kube_rootca_update()
self.assertEqual(result.state,
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
def test_create_failed_retry(self):
# Test creation of kubernetes rootca host update
# Allow retry update if update on this host ever failed
create_dict = {'phase': self.phase}
# overall update in progress with some hosts updated
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS_FAILED)
# root CA update on host ever failed
dbutils.create_test_kube_rootca_host_update(host_id=self.host.id,
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS_FAILED)
result = self.post_json(self.post_url, create_dict,
headers=self.headers)
# Verify that the rootca host update has the expected attributes
self.assertEqual(result.json['state'],
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
# Verify that the overall rootca update has the expected attributes
result = dbutils.get_kube_rootca_update()
self.assertEqual(result.state,
kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
def test_create_failed_update_not_started(self):
# Test creation failed since update not started yet
create_dict = {'phase': self.phase}
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-host-update rejected: "
"No update in progress", result.json['error_message'])
def test_create_failed_no_cert_available(self):
# Test creation failed since no new cert uploaded or generated
create_dict = {'phase': self.phase}
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATE_STARTED)
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-host-update rejected: No new certificate "
"available", result.json['error_message'])
def test_create_failed_host_update_completed(self):
# Test creation failed since this host already updated
create_dict = {'phase': self.phase}
# Overall update is in progress
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
# This host has been updated
dbutils.create_test_kube_rootca_host_update(host_id=self.host.id,
state=kubernetes.KUBE_ROOTCA_UPDATED_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-host-update rejected: update already "
"completed on host %s" % self.host.hostname,
result.json['error_message'])
def test_create_failed_hosts_update_in_progress(self):
# Test creation failed since there is update in progess on a host
create_dict = {'phase': self.phase}
# overall update in progress with some hosts updated
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS)
# root CA update on host2 is in progress
dbutils.create_test_kube_rootca_host_update(host_id=self.host2.id,
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-host-update rejected: update in progess "
"on host %s" % self.host2.hostname,
result.json['error_message'])
def test_create_failed_hosts_update_failed(self):
# Test creation failed since there is host update failed on a host
create_dict = {'phase': self.phase}
# overall update in progress with some hosts updated
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS_FAILED)
# root CA update on host2 failed
dbutils.create_test_kube_rootca_host_update(host_id=self.host2.id,
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS_FAILED)
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-host-update rejected: not allowed when "
"cluster update is in state: %s"
% kubernetes.KUBE_ROOTCA_UPDATING_HOST_TRUSTBOTHCAS_FAILED,
result.json['error_message'])
def test_create_failed_not_in_correct_state_updatecerts_failed(self):
# Test creation failed when user tries this phase after the overall
# update already passes this phase.
create_dict = {'phase': self.phase}
# overall update is in updateCerts phase
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_UPDATECERTS_FAILED)
# root CA update phase updateCerts on host failed
dbutils.create_test_kube_rootca_host_update(host_id=self.host.id,
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_UPDATECERTS_FAILED)
result = self.post_json(self.post_url, create_dict,
headers=self.headers,
expect_errors=True)
# but client make a call to perform update phase trust-both-cas
self.assertEqual(http_client.BAD_REQUEST, result.status_int)
self.assertIn("kube-rootca-host-update rejected: not allowed when "
"cluster update is in state: %s"
% kubernetes.KUBE_ROOTCA_UPDATING_HOST_UPDATECERTS_FAILED,
result.json['error_message'])
def test_create_failed_not_in_correct_state_updatecerts_in_progress(self):
# Test creation failed when user tries this phase after the overall
# update already passes this phase.
create_dict = {'phase': self.phase}
# overall update is in updateCerts phase
dbutils.create_test_kube_rootca_update(
state=kubernetes.KUBE_ROOTCA_UPDATING_HOST_UPDATECERTS)
# root CA update phase updateCerts completed on this host
dbutils.create_test_kube_rootca_host_update(host_id=self.host.id,
state=kubernetes.KUBE_ROOTCA_UPDATED_HOST_UPDATECERTS)
# but client make a call to perform update phase trust-both-cas
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-host-update rejected: not allowed when "
"cluster update is in state: %s"
% kubernetes.KUBE_ROOTCA_UPDATING_HOST_UPDATECERTS,
result.json['error_message'])

View File

@ -21,6 +21,7 @@ class PuppetTestCaseMixin(object):
mock.patch('sysinv.common.utils.is_virtual', return_value=False).start()
mock.patch('sysinv.puppet.kubernetes.KubernetesPuppet._get_host_join_command',
return_value={}).start()
mock.patch('sysinv.common.kubernetes.KubeOperator.kube_get_secret').start()
def assertConfigParameters(self, mock_write_config, parameters):
"""Validate the configuration contains the supplied parameters"""