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:
parent
d92bb6f98c
commit
33e24936d3
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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"""
|
||||
|
|
Loading…
Reference in New Issue