679 lines
30 KiB
Python
Executable File
679 lines
30 KiB
Python
Executable File
#
|
|
# Copyright (c) 2019-2023 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
from fm_api import constants as fm_constants
|
|
from fm_api import fm_api
|
|
|
|
import pecan
|
|
from pecan import rest
|
|
import os
|
|
import six
|
|
import wsme
|
|
from wsme import types as wtypes
|
|
import wsmeext.pecan as wsme_pecan
|
|
|
|
from oslo_log import log
|
|
|
|
from sysinv._i18n import _
|
|
from sysinv.api.controllers.v1 import base
|
|
from sysinv.api.controllers.v1 import collection
|
|
from sysinv.api.controllers.v1 import link
|
|
from sysinv.api.controllers.v1 import patch_api
|
|
from sysinv.api.controllers.v1 import types
|
|
from sysinv.common import constants
|
|
from sysinv.common import dc_api
|
|
from sysinv.common import exception
|
|
from sysinv.common import kubernetes
|
|
from sysinv.common import utils as cutils
|
|
from sysinv import objects
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class KubeUpgradePatchType(types.JsonPatchType):
|
|
|
|
@staticmethod
|
|
def mandatory_attrs():
|
|
return ['/state']
|
|
|
|
|
|
class KubeUpgrade(base.APIBase):
|
|
"""API representation of a Kubernetes Upgrade."""
|
|
|
|
id = int
|
|
"Unique ID for this entry"
|
|
|
|
uuid = types.uuid
|
|
"Unique UUID for this entry"
|
|
|
|
from_version = wtypes.text
|
|
"The from version for the kubernetes upgrade"
|
|
|
|
to_version = wtypes.text
|
|
"The to version for the kubernetes upgrade"
|
|
|
|
state = wtypes.text
|
|
"Kubernetes upgrade state"
|
|
|
|
links = [link.Link]
|
|
"A list containing a self link and associated kubernetes upgrade links"
|
|
|
|
def __init__(self, **kwargs):
|
|
self.fields = list(objects.kube_upgrade.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, rpc_kube_upgrade, expand=True):
|
|
kube_upgrade = KubeUpgrade(**rpc_kube_upgrade.as_dict())
|
|
if not expand:
|
|
kube_upgrade.unset_fields_except(['uuid', 'from_version',
|
|
'to_version', 'state'])
|
|
|
|
kube_upgrade.links = [
|
|
link.Link.make_link('self', pecan.request.host_url,
|
|
'kube_upgrade', kube_upgrade.uuid),
|
|
link.Link.make_link('bookmark',
|
|
pecan.request.host_url,
|
|
'kube_upgrade', kube_upgrade.uuid,
|
|
bookmark=True)
|
|
]
|
|
return kube_upgrade
|
|
|
|
|
|
class KubeUpgradeCollection(collection.Collection):
|
|
"""API representation of a collection of kubernetes upgrades."""
|
|
|
|
kube_upgrades = [KubeUpgrade]
|
|
"A list containing kubernetes upgrade objects"
|
|
|
|
def __init__(self, **kwargs):
|
|
self._type = 'kube_upgrades'
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_kube_upgrade, expand=True, **kwargs):
|
|
collection = KubeUpgradeCollection()
|
|
collection.kube_upgrades = [KubeUpgrade.convert_with_links(p, expand)
|
|
for p in rpc_kube_upgrade]
|
|
return collection
|
|
|
|
|
|
LOCK_NAME = 'KubeUpgradeController'
|
|
|
|
|
|
class KubeUpgradeController(rest.RestController):
|
|
"""REST controller for kubernetes upgrades."""
|
|
|
|
def __init__(self):
|
|
self._kube_operator = kubernetes.KubeOperator()
|
|
|
|
def _get_updates(self, patch):
|
|
"""Retrieve the updated attributes from the patch request."""
|
|
updates = {}
|
|
for p in patch:
|
|
attribute = p['path'] if p['path'][0] != '/' else p['path'][1:]
|
|
updates[attribute] = p['value']
|
|
return updates
|
|
|
|
@staticmethod
|
|
def _check_patch_requirements(region_name,
|
|
applied_patches=None,
|
|
available_patches=None):
|
|
"""Checks whether specified patches are applied or available"""
|
|
|
|
api_token = None
|
|
if applied_patches:
|
|
patches_applied = patch_api.patch_is_applied(
|
|
token=api_token,
|
|
timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
|
|
region_name=region_name,
|
|
patches=applied_patches
|
|
)
|
|
if not patches_applied:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"The following patches must be applied before doing "
|
|
"the kubernetes upgrade: %s" % applied_patches))
|
|
|
|
if available_patches:
|
|
patches_available = patch_api.patch_is_available(
|
|
token=api_token,
|
|
timeout=constants.PATCH_DEFAULT_TIMEOUT_IN_SECS,
|
|
region_name=region_name,
|
|
patches=available_patches
|
|
)
|
|
if not patches_available:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"The following patches must be available before doing "
|
|
"the kubernetes upgrade: %s" %
|
|
available_patches))
|
|
|
|
@wsme_pecan.wsexpose(KubeUpgradeCollection)
|
|
def get_all(self):
|
|
"""Retrieve a list of kubernetes upgrades."""
|
|
|
|
kube_upgrades = pecan.request.dbapi.kube_upgrade_get_list()
|
|
return KubeUpgradeCollection.convert_with_links(kube_upgrades)
|
|
|
|
@wsme_pecan.wsexpose(KubeUpgrade, types.uuid)
|
|
def get_one(self, uuid):
|
|
"""Retrieve information about the given kubernetes upgrade."""
|
|
|
|
rpc_kube_upgrade = objects.kube_upgrade.get_by_uuid(
|
|
pecan.request.context, uuid)
|
|
return KubeUpgrade.convert_with_links(rpc_kube_upgrade)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme_pecan.wsexpose(KubeUpgrade, wtypes.text, body=six.text_type)
|
|
def post(self, to_version, body):
|
|
"""Create a new Kubernetes Upgrade and start upgrade."""
|
|
|
|
force = body.get('force', False) is True
|
|
alarm_ignore_list = body.get('alarm_ignore_list')
|
|
system = pecan.request.dbapi.isystem_get_one()
|
|
retry = False
|
|
|
|
# There must not be a platform upgrade in progress
|
|
try:
|
|
pecan.request.dbapi.software_upgrade_get_one()
|
|
except exception.NotFound:
|
|
pass
|
|
else:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"A kubernetes upgrade cannot be done while a platform upgrade "
|
|
"is in progress"))
|
|
|
|
# There must not already be a kubernetes upgrade in progress
|
|
try:
|
|
kube_upgrade_obj = objects.kube_upgrade.get_one(pecan.request.context)
|
|
except exception.NotFound:
|
|
pass
|
|
else:
|
|
# Allow retrying the new Kubernetes upgrade if the current
|
|
# state is 'upgrade-starting-failed'.
|
|
if (kube_upgrade_obj.state == kubernetes.KUBE_UPGRADE_STARTING_FAILED and
|
|
kube_upgrade_obj.to_version == to_version):
|
|
retry = True
|
|
if alarm_ignore_list is None:
|
|
alarm_ignore_list = []
|
|
alarm_ignore_list.append(fm_constants.FM_ALARM_ID_KUBE_UPGRADE_IN_PROGRESS)
|
|
else:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"A kubernetes upgrade is already in progress"))
|
|
|
|
# Check whether target version is available or not
|
|
try:
|
|
target_version_obj = objects.kube_version.get_by_version(
|
|
to_version)
|
|
except exception.KubeVersionNotFound:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes version %s is not available" % to_version))
|
|
|
|
# The upgrade path must be supported
|
|
current_kube_version = self._kube_operator.kube_get_kubernetes_version()
|
|
version_states = self._kube_operator.kube_get_version_states()
|
|
|
|
# The target version must be available state
|
|
if system.system_mode == constants.SYSTEM_MODE_SIMPLEX:
|
|
if version_states.get(to_version) != kubernetes.KUBE_STATE_AVAILABLE:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"The target Kubernetes version %s is not in "
|
|
"available state" % (target_version_obj.version)))
|
|
else:
|
|
if not target_version_obj.can_upgrade_from(current_kube_version):
|
|
raise wsme.exc.ClientSideError(_(
|
|
"The installed Kubernetes version %s cannot upgrade to "
|
|
"version %s" % (current_kube_version,
|
|
target_version_obj.version)))
|
|
|
|
# The current kubernetes version must be active
|
|
if version_states.get(current_kube_version) != \
|
|
kubernetes.KUBE_STATE_ACTIVE:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"The installed Kubernetes version %s is not active on all "
|
|
"hosts" % current_kube_version))
|
|
|
|
# Verify patching requirements
|
|
system = pecan.request.dbapi.isystem_get_one()
|
|
self._check_patch_requirements(
|
|
system.region_name,
|
|
applied_patches=target_version_obj.applied_patches,
|
|
available_patches=target_version_obj.available_patches)
|
|
|
|
# The system must be healthy
|
|
success, output = pecan.request.rpcapi.get_system_health(
|
|
pecan.request.context,
|
|
force=force,
|
|
kube_upgrade=True,
|
|
alarm_ignore_list=alarm_ignore_list)
|
|
if not success:
|
|
LOG.info("Health query failure during kubernetes upgrade start: %s"
|
|
% output)
|
|
if os.path.exists(constants.SYSINV_RUNNING_IN_LAB) and force:
|
|
LOG.info("Running in lab, ignoring health errors.")
|
|
else:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"System is not in a valid state for kubernetes upgrade. "
|
|
"Run system health-query-kube-upgrade for more details."))
|
|
|
|
if retry:
|
|
# Update upgrade record
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADE_STARTING
|
|
kube_upgrade_obj.save()
|
|
else:
|
|
# Create upgrade record.
|
|
create_values = {'from_version': current_kube_version,
|
|
'to_version': to_version,
|
|
'state': kubernetes.KUBE_UPGRADE_STARTING}
|
|
kube_upgrade_obj = pecan.request.dbapi.kube_upgrade_create(create_values)
|
|
|
|
try:
|
|
# Set the target version for each host to the current version
|
|
update_values = {'target_version': current_kube_version}
|
|
kube_host_upgrades = pecan.request.dbapi.kube_host_upgrade_get_list()
|
|
for kube_host_upgrade in kube_host_upgrades:
|
|
pecan.request.dbapi.kube_host_upgrade_update(kube_host_upgrade.id,
|
|
update_values)
|
|
# Raise alarm to show a kubernetes upgrade is in progress
|
|
entity_instance_id = "%s=%s" % (fm_constants.FM_ENTITY_TYPE_HOST,
|
|
constants.CONTROLLER_HOSTNAME)
|
|
fault = fm_api.Fault(
|
|
alarm_id=fm_constants.FM_ALARM_ID_KUBE_UPGRADE_IN_PROGRESS,
|
|
alarm_state=fm_constants.FM_ALARM_STATE_SET,
|
|
entity_type_id=fm_constants.FM_ENTITY_TYPE_HOST,
|
|
entity_instance_id=entity_instance_id,
|
|
severity=fm_constants.FM_ALARM_SEVERITY_MINOR,
|
|
reason_text="Kubernetes upgrade in progress.",
|
|
# operational
|
|
alarm_type=fm_constants.FM_ALARM_TYPE_7,
|
|
# congestion
|
|
probable_cause=fm_constants.ALARM_PROBABLE_CAUSE_8,
|
|
proposed_repair_action="No action required.",
|
|
service_affecting=False)
|
|
fm_api.FaultAPIs().set_fault(fault)
|
|
|
|
# Set the new kubeadm version in the DB.
|
|
# This will not actually change the bind mounts until we apply a
|
|
# puppet manifest that makes use of it.
|
|
kube_cmd_versions = objects.kube_cmd_version.get(
|
|
pecan.request.context)
|
|
kube_cmd_versions.kubeadm_version = to_version.lstrip('v')
|
|
kube_cmd_versions.save()
|
|
|
|
LOG.info("Starting kubernetes upgrade from version: %s to version: %s"
|
|
% (current_kube_version, to_version))
|
|
|
|
# Tell the conductor to update the required apps and mark the upgrade as started
|
|
pecan.request.rpcapi.kube_upgrade_start(
|
|
pecan.request.context,
|
|
to_version)
|
|
except Exception as e:
|
|
LOG.exception("Failed to start Kubernetes upgrade: %s" % e)
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADE_STARTING_FAILED
|
|
kube_upgrade_obj.save()
|
|
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme.validate([KubeUpgradePatchType])
|
|
@wsme_pecan.wsexpose(KubeUpgrade, body=[KubeUpgradePatchType])
|
|
def patch(self, patch):
|
|
"""Updates attributes of a Kubernetes Upgrade."""
|
|
|
|
updates = self._get_updates(patch)
|
|
|
|
# Get the current upgrade
|
|
try:
|
|
kube_upgrade_obj = objects.kube_upgrade.get_one(
|
|
pecan.request.context)
|
|
except exception.NotFound:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"A kubernetes upgrade is not in progress"))
|
|
|
|
if updates['state'] and updates['state'].split('-')[-1] == 'failed':
|
|
if kube_upgrade_obj.state in [
|
|
kubernetes.KUBE_UPGRADE_DOWNLOADING_IMAGES,
|
|
kubernetes.KUBE_UPGRADING_FIRST_MASTER,
|
|
kubernetes.KUBE_UPGRADING_SECOND_MASTER,
|
|
kubernetes.KUBE_UPGRADING_STORAGE,
|
|
kubernetes.KUBE_UPGRADING_NETWORKING]:
|
|
kube_upgrade_obj.state = updates['state']
|
|
kube_upgrade_obj.save()
|
|
LOG.info("Kubernetes upgrade state is changed to %s" % updates['state'])
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
else:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"A kubernetes upgrade is in %s state cannot be set to failed"
|
|
% kube_upgrade_obj.state))
|
|
|
|
elif updates['state'] == kubernetes.KUBE_UPGRADE_DOWNLOADING_IMAGES:
|
|
# Make sure upgrade is in the correct state to download images
|
|
if kube_upgrade_obj.state not in [
|
|
kubernetes.KUBE_UPGRADE_STARTED,
|
|
kubernetes.KUBE_UPGRADE_DOWNLOADING_IMAGES_FAILED]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s or %s state to download "
|
|
"images" %
|
|
(kubernetes.KUBE_UPGRADE_STARTED,
|
|
kubernetes.KUBE_UPGRADE_DOWNLOADING_IMAGES_FAILED)))
|
|
|
|
# Verify patching requirements (since the api server is not
|
|
# upgraded yet, patches could have been removed)
|
|
system = pecan.request.dbapi.isystem_get_one()
|
|
target_version_obj = objects.kube_version.get_by_version(
|
|
kube_upgrade_obj.to_version)
|
|
self._check_patch_requirements(
|
|
system.region_name,
|
|
applied_patches=target_version_obj.applied_patches,
|
|
available_patches=target_version_obj.available_patches)
|
|
|
|
# Update the upgrade state
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADE_DOWNLOADING_IMAGES
|
|
kube_upgrade_obj.save()
|
|
|
|
# Tell the conductor to download the images for the new version
|
|
pecan.request.rpcapi.kube_download_images(
|
|
pecan.request.context, kube_upgrade_obj.to_version)
|
|
|
|
LOG.info("Downloading kubernetes images for version: %s" %
|
|
kube_upgrade_obj.to_version)
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
|
|
elif updates['state'] == kubernetes.KUBE_UPGRADING_NETWORKING:
|
|
# Make sure upgrade is in the correct state to upgrade networking
|
|
if kube_upgrade_obj.state not in [
|
|
kubernetes.KUBE_UPGRADE_DOWNLOADED_IMAGES,
|
|
kubernetes.KUBE_UPGRADING_NETWORKING_FAILED]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s or %s state to "
|
|
"upgrade networking" %
|
|
(kubernetes.KUBE_UPGRADE_DOWNLOADED_IMAGES,
|
|
kubernetes.KUBE_UPGRADING_NETWORKING_FAILED)))
|
|
|
|
# Update the upgrade state
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADING_NETWORKING
|
|
kube_upgrade_obj.save()
|
|
|
|
# Tell the conductor to upgrade networking
|
|
pecan.request.rpcapi.kube_upgrade_networking(
|
|
pecan.request.context, kube_upgrade_obj.to_version)
|
|
|
|
LOG.info("Upgrading kubernetes networking to version: %s" %
|
|
kube_upgrade_obj.to_version)
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
|
|
elif updates['state'] == kubernetes.KUBE_UPGRADING_STORAGE:
|
|
|
|
# Make sure upgrade is in the correct state to upgrade storage
|
|
if kube_upgrade_obj.state not in [
|
|
kubernetes.KUBE_UPGRADED_NETWORKING,
|
|
kubernetes.KUBE_UPGRADING_STORAGE_FAILED]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s or %s state to "
|
|
"upgrade storage" %
|
|
(kubernetes.KUBE_UPGRADED_NETWORKING,
|
|
kubernetes.KUBE_UPGRADING_STORAGE_FAILED)))
|
|
|
|
# Update the upgrade state
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADING_STORAGE
|
|
kube_upgrade_obj.save()
|
|
|
|
# Tell the conductor to upgrade storage
|
|
pecan.request.rpcapi.kube_upgrade_storage(
|
|
pecan.request.context, kube_upgrade_obj.to_version)
|
|
|
|
LOG.info("Upgrading kubernetes storage to version: %s" %
|
|
kube_upgrade_obj.to_version)
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
|
|
elif updates['state'] == kubernetes.KUBE_UPGRADE_ABORTING:
|
|
system = pecan.request.dbapi.isystem_get_one()
|
|
if system.system_mode != constants.SYSTEM_MODE_SIMPLEX:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"The 'system kube-upgrade-abort' is not supported "
|
|
"in %s" % system.system_mode))
|
|
if kube_upgrade_obj.state in [kubernetes.KUBE_UPGRADE_ABORTING,
|
|
kubernetes.KUBE_UPGRADE_ABORTED,
|
|
kubernetes.KUBE_UPGRADE_COMPLETE]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Cannot abort the kubernetes upgrade it is in %s state" %
|
|
(kube_upgrade_obj.state)))
|
|
|
|
# Assign the original state of the k8s upgrade before the abort.
|
|
kube_state = kube_upgrade_obj.state
|
|
# Restore the kube upgrade target version for each host to the from_version
|
|
# and set the status as aborting.
|
|
update_values = {'target_version': kube_upgrade_obj.from_version,
|
|
'status': kubernetes.KUBE_UPGRADE_ABORTING}
|
|
kube_host_upgrades = pecan.request.dbapi.kube_host_upgrade_get_list()
|
|
for kube_host_upgrade in kube_host_upgrades:
|
|
pecan.request.dbapi.kube_host_upgrade_update(kube_host_upgrade.id,
|
|
update_values)
|
|
# Restore the kubeadm_version and kubelet_version to the from_version
|
|
kube_cmd_versions = objects.kube_cmd_version.get(pecan.request.context)
|
|
kube_cmd_versions.kubeadm_version = kube_upgrade_obj.from_version.lstrip('v')
|
|
kube_cmd_versions.kubelet_version = kube_upgrade_obj.from_version.lstrip('v')
|
|
kube_cmd_versions.save()
|
|
|
|
# Update the state as aborted for these states since no actual k8s changes done
|
|
# so we don't need to do anything more to complete the abort.
|
|
if kube_upgrade_obj.state in [kubernetes.KUBE_UPGRADE_STARTING,
|
|
kubernetes.KUBE_UPGRADE_STARTING_FAILED,
|
|
kubernetes.KUBE_UPGRADE_STARTED,
|
|
kubernetes.KUBE_UPGRADE_DOWNLOADING_IMAGES,
|
|
kubernetes.KUBE_UPGRADE_DOWNLOADING_IMAGES_FAILED,
|
|
kubernetes.KUBE_UPGRADE_DOWNLOADED_IMAGES]:
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADE_ABORTED
|
|
kube_upgrade_obj.save()
|
|
else:
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADE_ABORTING
|
|
kube_upgrade_obj.save()
|
|
|
|
# Tell the conductor to abort k8s upgrade
|
|
pecan.request.rpcapi.kube_upgrade_abort(
|
|
pecan.request.context, kube_state)
|
|
|
|
LOG.info("Aborting kubernetes upgrade version: %s" %
|
|
kube_upgrade_obj.to_version)
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
|
|
elif updates['state'] == kubernetes.KUBE_UPGRADE_CORDON:
|
|
system = pecan.request.dbapi.isystem_get_one()
|
|
if system.system_mode != constants.SYSTEM_MODE_SIMPLEX:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"The 'system kube-host-cordon' is not supported "
|
|
"in %s" % system.system_mode))
|
|
# Make sure upgrade is in the correct state to cordon
|
|
if kube_upgrade_obj.state not in [
|
|
kubernetes.KUBE_UPGRADED_NETWORKING,
|
|
kubernetes.KUBE_UPGRADED_STORAGE,
|
|
kubernetes.KUBE_UPGRADE_CORDON_FAILED]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s, %s or %s state "
|
|
"to cordon" %
|
|
(kubernetes.KUBE_UPGRADED_NETWORKING,
|
|
kubernetes.KUBE_UPGRADED_STORAGE,
|
|
kubernetes.KUBE_UPGRADE_CORDON_FAILED)))
|
|
|
|
# Update the upgrade state
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADE_CORDON
|
|
kube_upgrade_obj.save()
|
|
|
|
# Tell the conductor to cordon the pods to evict from the host
|
|
pecan.request.rpcapi.kube_host_cordon(
|
|
pecan.request.context, updates['hostname'])
|
|
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
|
|
elif updates['state'] == kubernetes.KUBE_UPGRADE_UNCORDON:
|
|
system = pecan.request.dbapi.isystem_get_one()
|
|
if system.system_mode != constants.SYSTEM_MODE_SIMPLEX:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"The system kube-host-uncordon is not supported "
|
|
"in %s" % system.system_mode))
|
|
# Make sure upgrade is in the correct state to uncordon
|
|
if kube_upgrade_obj.state not in [
|
|
kubernetes.KUBE_UPGRADING_KUBELETS,
|
|
kubernetes.KUBE_UPGRADE_UNCORDON_FAILED]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s or %s state to "
|
|
"uncordon" %
|
|
(kubernetes.KUBE_UPGRADING_KUBELETS,
|
|
kubernetes.KUBE_UPGRADE_UNCORDON_FAILED)))
|
|
|
|
# Make sure no hosts are in a transitory or failed state
|
|
kube_host_upgrades = \
|
|
pecan.request.dbapi.kube_host_upgrade_get_list()
|
|
for kube_host_upgrade in kube_host_upgrades:
|
|
if kube_host_upgrade.status != \
|
|
kubernetes.KUBE_HOST_UPGRADED_KUBELET:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"At least one host has not completed the kubelet "
|
|
"upgrade"))
|
|
|
|
# Update the upgrade state
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADE_UNCORDON
|
|
kube_upgrade_obj.save()
|
|
|
|
# Tell the conductor to allow the evicted pods on the host again
|
|
pecan.request.rpcapi.kube_host_uncordon(
|
|
pecan.request.context, updates['hostname'])
|
|
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
|
|
elif updates['state'] == kubernetes.KUBE_UPGRADE_COMPLETE:
|
|
# Make sure upgrade is in the correct state to complete
|
|
system = pecan.request.dbapi.isystem_get_one()
|
|
|
|
if system.system_mode == constants.SYSTEM_MODE_SIMPLEX:
|
|
# If the node is unschedulable=True then the cordon command
|
|
# executed already and some of the pods are in pending status.
|
|
# The uncordon command needs to be triggered.
|
|
# If the node is unschedulable=None then the pods are in the
|
|
# running status.
|
|
unschedulable = None
|
|
node_status = self._kube_operator.kube_get_node_status(constants.CONTROLLER_0_HOSTNAME)
|
|
LOG.debug("Node status: %s" % node_status)
|
|
if node_status:
|
|
unschedulable = node_status.spec.unschedulable
|
|
|
|
if unschedulable:
|
|
if kube_upgrade_obj.state not in [
|
|
kubernetes.KUBE_UPGRADE_UNCORDON_COMPLETE]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s state to complete" %
|
|
kubernetes.KUBE_UPGRADE_UNCORDON_COMPLETE))
|
|
else:
|
|
if kube_upgrade_obj.state not in [
|
|
kubernetes.KUBE_UPGRADING_KUBELETS,
|
|
kubernetes.KUBE_UPGRADE_UNCORDON_COMPLETE]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s or %s state to complete" %
|
|
(kubernetes.KUBE_UPGRADING_KUBELETS,
|
|
kubernetes.KUBE_UPGRADE_UNCORDON_COMPLETE)))
|
|
else:
|
|
if kube_upgrade_obj.state not in [
|
|
kubernetes.KUBE_UPGRADING_KUBELETS]:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s state to complete" %
|
|
kubernetes.KUBE_UPGRADING_KUBELETS))
|
|
|
|
# Make sure no hosts are in a transitory or failed state
|
|
kube_host_upgrades = \
|
|
pecan.request.dbapi.kube_host_upgrade_get_list()
|
|
for kube_host_upgrade in kube_host_upgrades:
|
|
if kube_host_upgrade.status != \
|
|
kubernetes.KUBE_HOST_UPGRADED_KUBELET:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"At least one host has not completed the kubernetes "
|
|
"upgrade"))
|
|
|
|
# Make sure the target version is active
|
|
version_states = self._kube_operator.kube_get_version_states()
|
|
if version_states.get(kube_upgrade_obj.to_version, None) != \
|
|
kubernetes.KUBE_STATE_ACTIVE:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes to_version must be active to complete"))
|
|
|
|
# Set the new kubelet version in the DB.
|
|
kube_cmd_versions = objects.kube_cmd_version.get(
|
|
pecan.request.context)
|
|
kube_cmd_versions.kubelet_version = kube_upgrade_obj.to_version.lstrip('v')
|
|
kube_cmd_versions.save()
|
|
|
|
# The global kubelet version is set, clear the per-host status.
|
|
for kube_host_upgrade in kube_host_upgrades:
|
|
pecan.request.dbapi.kube_host_upgrade_update(
|
|
kube_host_upgrade.id, {'status': None})
|
|
|
|
# All is well, mark the upgrade as complete
|
|
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADE_COMPLETE
|
|
kube_upgrade_obj.save()
|
|
|
|
role = system.get('distributed_cloud_role')
|
|
# Clean up container images for the system other than systemcontroller
|
|
if role != constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER:
|
|
pecan.request.rpcapi.kube_delete_container_images(
|
|
pecan.request.context, kube_upgrade_obj.to_version)
|
|
|
|
LOG.info("Completed kubernetes upgrade to version: %s" %
|
|
kube_upgrade_obj.to_version)
|
|
|
|
# If applicable, notify dcmanager upgrade is complete
|
|
if role == constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER:
|
|
dc_api.notify_dcmanager_kubernetes_upgrade_completed()
|
|
|
|
# Update apps that contain 'k8s_upgrade.timing = post' metadata
|
|
pecan.request.rpcapi.update_apps_based_on_k8s_version_async(
|
|
pecan.request.context,
|
|
kube_upgrade_obj.to_version,
|
|
constants.APP_METADATA_TIMING_POST)
|
|
|
|
# Check if apps need to be reapplied
|
|
pecan.request.rpcapi.evaluate_apps_reapply(
|
|
pecan.request.context,
|
|
trigger={'type': constants.APP_EVALUATE_REAPPLY_TYPE_KUBE_UPGRADE_COMPLETE})
|
|
|
|
return KubeUpgrade.convert_with_links(kube_upgrade_obj)
|
|
|
|
else:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Invalid state %s supplied" % updates['state']))
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme_pecan.wsexpose(None)
|
|
def delete(self):
|
|
"""Delete Kubernetes Upgrade."""
|
|
|
|
# An upgrade must be in progress
|
|
try:
|
|
kube_upgrade_obj = pecan.request.dbapi.kube_upgrade_get_one()
|
|
except exception.NotFound:
|
|
raise wsme.exc.ClientSideError(_(
|
|
"A kubernetes upgrade is not in progress"))
|
|
if kube_upgrade_obj.state not in [kubernetes.KUBE_UPGRADE_COMPLETE,
|
|
kubernetes.KUBE_UPGRADE_ABORTED]:
|
|
# The upgrade must be in complete or abort state to delete
|
|
raise wsme.exc.ClientSideError(_(
|
|
"Kubernetes upgrade must be in %s or %s state to delete" %
|
|
(kubernetes.KUBE_UPGRADE_COMPLETE,
|
|
kubernetes.KUBE_UPGRADE_ABORTED)))
|
|
|
|
# Clean up k8s control-plane backup
|
|
pecan.request.rpcapi.remove_kube_control_plane_backup(
|
|
pecan.request.context)
|
|
|
|
# Delete the upgrade
|
|
pecan.request.dbapi.kube_upgrade_destroy(kube_upgrade_obj.id)
|
|
|
|
# Clear the kubernetes upgrade alarm
|
|
entity_instance_id = "%s=%s" % (fm_constants.FM_ENTITY_TYPE_HOST,
|
|
constants.CONTROLLER_HOSTNAME)
|
|
fm_api.FaultAPIs().clear_fault(
|
|
fm_constants.FM_ALARM_ID_KUBE_UPGRADE_IN_PROGRESS,
|
|
entity_instance_id)
|