config/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/service_parameter.py

985 lines
40 KiB
Python

# Copyright (c) 2015-2022 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
#
import copy
import json
import os
import pecan
from pecan import rest
import six
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from fm_api import constants as fm_constants
from fm_api import fm_api
from oslo_log import log
from oslo_utils import excutils
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 types
from sysinv.api.controllers.v1 import utils
from sysinv.api.controllers.v1.query import Query
from sysinv.api.policies import service_parameter as sp_policy
from sysinv import objects
from sysinv.common import constants
from sysinv.common import exception
from sysinv.common import kubernetes
from sysinv.common import policy
from sysinv.common import service_parameter
from sysinv.common import utils as cutils
from sysinv.openstack.common.rpc import common as rpc_common
LOG = log.getLogger(__name__)
k8s_volumes_sections = [
constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER_VOLUMES,
constants.SERVICE_PARAM_SECTION_KUBERNETES_CONTROLLER_MANAGER_VOLUMES,
constants.SERVICE_PARAM_SECTION_KUBERNETES_SCHEDULER_VOLUMES]
def delete_k8s_configmap(parameter, kube_operator):
"""
The function removes the k8s configmap resource associated with an
extra-volume service parameter.
Parameters
----------
parameter : dict
Dictionary with service-parameter extra-volume data.
Raises
------
wsme.exc.ClientSideError
"""
_volume, _noConfigMap = service_parameter.parse_volume_string_to_dict(parameter)
# only delete configmaps for 'File' type since
# 'DirectoryorCreate' type has no associated configmaps
pathType = _volume['pathType']
if pathType != 'File':
return
configmap_name = service_parameter.get_k8s_configmap_name(parameter)
namespace = 'kube-system'
try:
kube_operator.kube_delete_config_map(
name=configmap_name, namespace=namespace)
except Exception as e:
msg = _("Failure deleting configmap: %s." % e)
raise wsme.exc.ClientSideError(msg)
def create_k8s_configmap(parameter, kube_operator):
"""
The function creates the k8s configmap resource associated with an
extra-volume service parameter.
Parameters
----------
parameter : dict
Dictionary with service-parameter extra-volume data.
Raises
------
wsme.exc.ClientSideError
"""
_volume, noConfigmap = service_parameter.parse_volume_string_to_dict(parameter)
# skip k8s configmap creation if 'noConfigmap' flag exists
# this case is used only during bootstrap initialization
if noConfigmap == 1:
# removes noConfigmap flag from the service parameter value, as it is
# no longer needed during runtime operation
parameter['value'] = json.dumps(_volume)
return parameter
# only create configmaps for 'File' type
# 'DirectoryorCreate' type has no associated configmaps
pathType = _volume['pathType']
if pathType != 'File':
return parameter
filename = _volume['hostPath']
configmap_name = service_parameter.get_k8s_configmap_name(parameter)
namespace = 'kube-system'
try:
# verifying if file exists
if not os.path.isfile(filename):
msg = _("File not found.")
raise wsme.exc.ClientSideError(msg)
# verify if configmap exists
if kube_operator.kube_get_config_map(configmap_name, namespace):
msg = ("Failed to create configmap %s, "
"configmap exists" % (configmap_name))
raise wsme.exc.ClientSideError(msg)
# create configmap
kube_operator.kube_create_config_map_from_file(
namespace, configmap_name, filename)
# verify configmap
try:
configmap = kube_operator.kube_read_config_map(
name=configmap_name, namespace=namespace)
if configmap is None:
msg = _("configmap not found.")
raise wsme.exc.ClientSideError(msg)
return parameter
except Exception as e:
msg = _("checking configmap: %s." % e)
raise wsme.exc.ClientSideError(msg)
except Exception as e:
msg = _("Configmap add failed: %s. Service parameter add aborted." % e)
raise wsme.exc.ClientSideError(msg)
def update_k8s_configmap(parameter, kube_operator):
"""
The function updates the k8s configmap resource associated with an
extra-volume service parameter.
Parameters
----------
parameter : dict
Dictionary with service-parameter extra-volume data.
Raises
------
wsme.exc.ClientSideError
"""
_volume, _noConfigMap = service_parameter.parse_volume_string_to_dict(parameter)
# only update configmaps for 'File' type since
# 'DirectoryorCreate' type has no associated configmaps
pathType = _volume['pathType']
if pathType != 'File':
return
delete_k8s_configmap(parameter, kube_operator)
create_k8s_configmap(parameter, kube_operator)
class ServiceParameterPatchType(types.JsonPatchType):
@staticmethod
def mandatory_attrs():
return ['/uuid']
class ServiceParameter(base.APIBase):
"""API representation of a Service Parameter instance.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of a service
parameter.
"""
id = int
"Unique ID for this entry"
uuid = types.uuid
"Unique UUID for this entry"
service = wtypes.text
"Name of a service."
section = wtypes.text
"Name of a section."
name = wtypes.text
"Name of a parameter"
value = wtypes.text
"Value of a parameter"
personality = wtypes.text
"The host personality to which the parameter is restricted."
resource = wtypes.text
"The puppet resource"
links = [link.Link]
"A list containing a self link and associated links"
def __init__(self, **kwargs):
self.fields = list(objects.service_parameter.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_service_parameter, expand=True):
parm = ServiceParameter(**rpc_service_parameter.as_dict())
if not expand:
parm.unset_fields_except(['uuid', 'service', 'section',
'name', 'value', 'personality', 'resource'])
parm.links = [link.Link.make_link('self', pecan.request.host_url,
'parameters', parm.uuid),
link.Link.make_link('bookmark',
pecan.request.host_url,
'parameters', parm.uuid,
bookmark=True)
]
return parm
class ServiceParameterCollection(collection.Collection):
"""API representation of a collection of service parameters."""
parameters = [ServiceParameter]
"A list containing Service Parameter objects"
def __init__(self, **kwargs):
self._type = 'parameters'
@classmethod
def convert_with_links(cls, rpc_service_parameter, limit, url=None,
expand=False,
**kwargs):
collection = ServiceParameterCollection()
collection.parameters = [ServiceParameter.convert_with_links(p, expand)
for p in rpc_service_parameter]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
LOCK_NAME = 'ServiceParameterController'
class ServiceParameterController(rest.RestController):
"""REST controller for ServiceParameter."""
_custom_actions = {
'apply': ['POST'],
}
def __init__(self, parent=None, **kwargs):
self._parent = parent
self.kube_operator = kubernetes.KubeOperator()
def _get_service_parameter_collection(self, marker=None, limit=None,
sort_key=None, sort_dir=None,
expand=False, resource_url=None,
q=None):
limit = utils.validate_limit(limit)
sort_dir = utils.validate_sort_dir(sort_dir)
kwargs = {}
if q is not None:
for i in q:
if i.op == 'eq':
kwargs[i.field] = i.value
marker_obj = None
if marker:
marker_obj = objects.service_parameter.get_by_uuid(
pecan.request.context, marker)
if q is None:
parms = pecan.request.dbapi.service_parameter_get_list(
limit=limit, marker=marker_obj,
sort_key=sort_key, sort_dir=sort_dir)
else:
kwargs['limit'] = limit
kwargs['sort_key'] = sort_key
kwargs['sort_dir'] = sort_dir
parms = pecan.request.dbapi.service_parameter_get_all(**kwargs)
# Before we can return the service parameter collection,
# we need to ensure that the list does not contain any
# "protected" service parameters which may need to be
# obfuscated.
for idx, svc_param in enumerate(parms):
service = svc_param['service']
section = svc_param['section']
name = svc_param['name']
if service in service_parameter.SERVICE_PARAMETER_SCHEMA \
and section in service_parameter.SERVICE_PARAMETER_SCHEMA[service]:
schema = service_parameter.SERVICE_PARAMETER_SCHEMA[service][section]
if service_parameter.SERVICE_PARAM_PROTECTED in schema:
# atleast one parameter is to be protected
if name in schema[service_parameter.SERVICE_PARAM_PROTECTED]:
parms[idx]['value'] = service_parameter.SERVICE_VALUE_PROTECTION_MASK
return ServiceParameterCollection.convert_with_links(
parms, limit, url=resource_url, expand=expand,
sort_key=sort_key, sort_dir=sort_dir)
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
@wsme_pecan.wsexpose(ServiceParameterCollection, [Query],
types.uuid, wtypes.text,
wtypes.text, wtypes.text, wtypes.text)
def get_all(self, q=None, marker=None, limit=None,
sort_key='id', sort_dir='asc'):
"""Retrieve a list of service parameters."""
if q is None:
q = []
sort_key = ['section', 'name']
return self._get_service_parameter_collection(marker, limit,
sort_key,
sort_dir, q=q)
@wsme_pecan.wsexpose(ServiceParameter, types.uuid)
def get_one(self, uuid):
"""Retrieve information about the given parameter."""
rpc_parameter = objects.service_parameter.get_by_uuid(
pecan.request.context, uuid)
# Before we can return the service parameter, we need
# to ensure that it is not a "protected" parameter
# which may need to be obfuscated.
service = rpc_parameter['service']
section = rpc_parameter['section']
name = rpc_parameter['name']
if service in service_parameter.SERVICE_PARAMETER_SCHEMA \
and section in service_parameter.SERVICE_PARAMETER_SCHEMA[service]:
schema = service_parameter.SERVICE_PARAMETER_SCHEMA[service][section]
if service_parameter.SERVICE_PARAM_PROTECTED in schema:
# parameter is to be protected
if name in schema[service_parameter.SERVICE_PARAM_PROTECTED]:
rpc_parameter['value'] = service_parameter.SERVICE_VALUE_PROTECTION_MASK
return ServiceParameter.convert_with_links(rpc_parameter)
@staticmethod
def _check_read_only_parameter(svc_param):
"""Check the read-only attribute of service parameter"""
service = svc_param['service']
section = svc_param['section']
name = svc_param['name']
schema = service_parameter.SERVICE_PARAMETER_SCHEMA[service][section]
readonly_parameters = schema.get(service_parameter.SERVICE_PARAM_READONLY, [])
if name in readonly_parameters:
msg = _("The parameter '%s' is readonly." % name)
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _check_parameter_syntax(svc_param):
"""Check the attributes of service parameter"""
service = svc_param['service']
section = svc_param['section']
name = svc_param['name']
value = svc_param['value']
if service == constants.SERVICE_TYPE_PTP:
msg = _("%s service is deprecated" % service)
raise wsme.exc.ClientSideError(msg)
schema = service_parameter.SERVICE_PARAMETER_SCHEMA[service][section]
parameters = (schema.get(service_parameter.SERVICE_PARAM_MANDATORY, []) +
schema.get(service_parameter.SERVICE_PARAM_OPTIONAL, []))
has_wildcard = (constants.SERVICE_PARAM_NAME_WILDCARD in parameters)
if name not in parameters and not has_wildcard:
msg = _("The parameter name %s is invalid for "
"service %s section %s"
% (name, service, section))
raise wsme.exc.ClientSideError(msg)
if not value:
msg = _("The service parameter value is mandatory")
raise wsme.exc.ClientSideError(msg)
if len(value) > service_parameter.SERVICE_PARAMETER_MAX_LENGTH:
msg = _("The service parameter value is restricted to at most %d "
"characters." % service_parameter.SERVICE_PARAMETER_MAX_LENGTH)
raise wsme.exc.ClientSideError(msg)
validators = schema.get(service_parameter.SERVICE_PARAM_VALIDATOR, {})
validator = validators.get(name)
if callable(validator):
validator(name, value)
@staticmethod
def _check_custom_parameter_syntax(svc_param):
"""Check the attributes of custom service parameter"""
service = svc_param['service']
section = svc_param['section']
name = svc_param['name']
value = svc_param['value']
personality = svc_param['personality']
resource = svc_param['resource']
if service == constants.SERVICE_TYPE_PTP:
msg = _("%s service is deprecated" % service)
raise wsme.exc.ClientSideError(msg)
if personality is not None and personality not in constants.PERSONALITIES:
msg = _("%s is not a supported personality type" % personality)
raise wsme.exc.ClientSideError(msg)
if len(resource) > service_parameter.SERVICE_PARAMETER_MAX_LENGTH:
msg = _("The custom resource option is restricted to at most %d "
"characters." % service_parameter.SERVICE_PARAMETER_MAX_LENGTH)
raise wsme.exc.ClientSideError(msg)
if service in service_parameter.SERVICE_PARAMETER_SCHEMA \
and section in service_parameter.SERVICE_PARAMETER_SCHEMA[service]:
schema = service_parameter.SERVICE_PARAMETER_SCHEMA[service][section]
parameters = (schema.get(service_parameter.SERVICE_PARAM_MANDATORY, []) +
schema.get(service_parameter.SERVICE_PARAM_OPTIONAL, []))
if name in parameters:
msg = _("The parameter name %s is reserved for "
"service %s section %s, and cannot be customized"
% (name, service, section))
raise wsme.exc.ClientSideError(msg)
if value is not None and len(value) > service_parameter.SERVICE_PARAMETER_MAX_LENGTH:
msg = _("The service parameter value is restricted to at most %d "
"characters." % service_parameter.SERVICE_PARAMETER_MAX_LENGTH)
raise wsme.exc.ClientSideError(msg)
mapped_resource = service_parameter.map_resource(resource)
if mapped_resource is not None:
msg = _("The specified resource is reserved for "
"service=%s section=%s name=%s and cannot "
"be customized."
% (mapped_resource.get('service'),
mapped_resource.get('section'),
mapped_resource.get('name')))
raise wsme.exc.ClientSideError(msg)
def post_custom_resource(self, body, personality, resource):
"""Create new custom Service Parameter."""
if resource is None:
raise wsme.exc.ClientSideError(_("Unspecified resource"))
service = body.get('service')
if not service:
raise wsme.exc.ClientSideError("Unspecified service name")
section = body.get('section')
if not section:
raise wsme.exc.ClientSideError(_("Unspecified section name."))
new_records = []
parameters = body.get('parameters')
if not parameters:
raise wsme.exc.ClientSideError(_("Unspecified parameters."))
if len(parameters) > 1:
msg = _("Cannot specify multiple parameters with custom resource.")
raise wsme.exc.ClientSideError(msg)
for name, value in parameters.items():
new_record = {
'service': service,
'section': section,
'name': name,
'value': value,
'personality': personality,
'resource': resource,
}
self._check_custom_parameter_syntax(new_record)
existing = False
try:
pecan.request.dbapi.service_parameter_get_one(
service, section, name,
personality, resource)
existing = True
except exception.NotFound:
pass
except exception.MultipleResults:
# We'll check/handle this in the "finally" block
existing = True
finally:
if existing:
msg = _("Service parameter add failed: "
"Parameter already exists: "
"service=%s section=%s name=%s "
"personality=%s resource=%s"
% (service, section, name,
personality, resource))
raise wsme.exc.ClientSideError(msg)
new_records.append(new_record)
svc_params = []
for n in new_records:
try:
new_parm = pecan.request.dbapi.service_parameter_create(n)
except exception.NotFound:
msg = _("Service parameter add failed: "
"service %s section %s name %s value %s"
" personality %s resource %s"
% (service, section, n.name, n.value, personality, resource))
raise wsme.exc.ClientSideError(msg)
svc_params.append(new_parm)
try:
pecan.request.rpcapi.update_service_config(
pecan.request.context, service, section=section)
except rpc_common.RemoteError as e:
# rollback create service parameters
for p in svc_params:
try:
pecan.request.dbapi.service_parameter_destroy_uuid(p.uuid)
LOG.warn(_("Rollback service parameter create: "
"destroy uuid {}".format(p.uuid)))
except exception.SysinvException:
pass
raise wsme.exc.ClientSideError(str(e.value))
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.exception(e)
return ServiceParameterCollection.convert_with_links(
svc_params, limit=None, url=None, expand=False,
sort_key='id', sort_dir='asc')
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(ServiceParameterCollection, body=types.apidict)
def post(self, body):
"""Create new Service Parameter."""
resource = body.get('resource')
personality = body.get('personality')
if personality is not None or resource is not None:
return self.post_custom_resource(body, personality, resource)
service = self._get_service(body)
section = body.get('section')
if not section:
raise wsme.exc.ClientSideError(_("Unspecified section name."))
elif section not in service_parameter.SERVICE_PARAMETER_SCHEMA[service]:
msg = _("Invalid service section %s." % section)
raise wsme.exc.ClientSideError(msg)
new_records = []
parameters = body.get('parameters')
if not parameters:
raise wsme.exc.ClientSideError(_("Unspecified parameters."))
for name, value in parameters.items():
new_record = {
'service': service,
'section': section,
'name': name,
'value': value,
}
self._check_parameter_syntax(new_record)
existing = False
try:
pecan.request.dbapi.service_parameter_get_one(
service, section, name)
existing = True
except exception.NotFound:
pass
except exception.MultipleResults:
# We'll check/handle this in the "finally" block
existing = True
finally:
if existing:
msg = _("Service parameter add failed: "
"Parameter already exists: "
"service=%s section=%s name=%s"
% (service, section, name))
raise wsme.exc.ClientSideError(msg)
new_records.append(new_record)
svc_params = []
for n in new_records:
# If the parameter being created belongs to the kube_apiserver_volumes,
# kube_controller_manager_volumes or scheduler_volumes sections and
# does not define a directory volume, a K8s configmap is
# created from the configuration file pointed by the parameter.
if n['section'] in k8s_volumes_sections:
n = create_k8s_configmap(n, self.kube_operator)
# creating new service-parameter in the sysinv database
try:
new_parm = pecan.request.dbapi.service_parameter_create(n)
except exception.NotFound:
msg = _("Service parameter add failed: "
"service %s section %s name %s value %s"
% (service, section, n.name, n.value))
raise wsme.exc.ClientSideError(msg)
svc_params.append(new_parm)
try:
# Pass name to update_service_config only in case the parameter is the Intel
# NIC driver version
new_name = None
if section == constants.SERVICE_PARAM_SECTION_PLATFORM_CONFIG and \
name == constants.SERVICE_PARAM_NAME_PLAT_CONFIG_INTEL_NIC_DRIVER_VERSION:
new_name = name
pecan.request.rpcapi.update_service_config(
pecan.request.context, service, section=section, name=new_name)
except rpc_common.RemoteError as e:
# rollback create service parameters
for p in svc_params:
try:
pecan.request.dbapi.service_parameter_destroy_uuid(p.uuid)
LOG.warn(_("Rollback service parameter create: "
"destroy uuid {}".format(p.uuid)))
except exception.SysinvException:
pass
raise wsme.exc.ClientSideError(str(e.value))
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.exception(e)
return ServiceParameterCollection.convert_with_links(
svc_params, limit=None, url=None, expand=False,
sort_key='id', sort_dir='asc')
def patch_custom_resource(self, uuid, patch, personality, resource):
"""Updates attributes of Service Parameter."""
parameter = objects.service_parameter.get_by_uuid(
pecan.request.context, uuid)
parameter = parameter.as_dict()
old_parameter = copy.deepcopy(parameter)
updates = self._get_updates(patch)
parameter.update(updates)
self._check_custom_parameter_syntax(parameter)
updated_parameter = pecan.request.dbapi.service_parameter_update(
uuid, updates)
try:
pecan.request.rpcapi.update_service_config(
pecan.request.context,
parameter['service'],
section=parameter['section'])
except rpc_common.RemoteError as e:
# rollback service parameter update
try:
pecan.request.dbapi.service_parameter_update(uuid, old_parameter)
LOG.warn(_("Rollback service parameter update: "
"uuid={}, old_values={}".format(uuid, old_parameter)))
except exception.SysinvException:
pass
raise wsme.exc.ClientSideError(str(e.value))
return ServiceParameter.convert_with_links(updated_parameter)
@cutils.synchronized(LOCK_NAME)
@wsme.validate(types.uuid, [ServiceParameterPatchType])
@wsme_pecan.wsexpose(ServiceParameter, types.uuid,
body=[ServiceParameterPatchType])
def patch(self, uuid, patch):
"""Updates attributes of Service Parameter."""
parameter = objects.service_parameter.get_by_uuid(
pecan.request.context, uuid)
if parameter.personality is not None or parameter.resource is not None:
return self.patch_custom_resource(uuid,
patch,
parameter.personality,
parameter.resource)
parameter = parameter.as_dict()
old_parameter = copy.deepcopy(parameter)
updates = self._get_updates(patch)
parameter.update(updates)
self._check_parameter_syntax(parameter)
self._check_read_only_parameter(parameter)
updated_parameter = pecan.request.dbapi.service_parameter_update(
uuid, updates)
# If the parameter being updated belongs to the kube_apiserver_volumes,
# kube_controller_manager_volumes or scheduler_volumes sections and
# does not define a directory volume, the K8s configmap is
# updated from the configuration file pointed by the parameter.
if parameter['section'] in k8s_volumes_sections:
update_k8s_configmap(parameter, self.kube_operator)
try:
if parameter['name'] not in service_parameter.DB_ONLY_SERVICE_PARAMETERS:
pecan.request.rpcapi.update_service_config(
pecan.request.context,
parameter['service'],
section=parameter['section'],
name=parameter['name'])
except rpc_common.RemoteError as e:
# rollback service parameter update
try:
pecan.request.dbapi.service_parameter_update(uuid, old_parameter)
LOG.warn(_("Rollback service parameter update: "
"uuid={}, old_values={}".format(uuid, old_parameter)))
except exception.SysinvException:
pass
raise wsme.exc.ClientSideError(str(e.value))
# Before we can return the service parameter, we need
# to ensure that this updated parameter is not "protected"
# which may need to be obfuscated.
service = updated_parameter['service']
section = updated_parameter['section']
name = updated_parameter['name']
if service in service_parameter.SERVICE_PARAMETER_SCHEMA \
and section in service_parameter.SERVICE_PARAMETER_SCHEMA[service]:
schema = service_parameter.SERVICE_PARAMETER_SCHEMA[service][section]
if service_parameter.SERVICE_PARAM_PROTECTED in schema:
# parameter is to be protected
if name in schema[service_parameter.SERVICE_PARAM_PROTECTED]:
updated_parameter['value'] = service_parameter.SERVICE_VALUE_PROTECTION_MASK
return ServiceParameter.convert_with_links(updated_parameter)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
def delete(self, uuid):
"""Delete a Service Parameter instance."""
parameter = objects.service_parameter.get_by_uuid(pecan.request.context, uuid)
if parameter.section == \
constants.SERVICE_PARAM_SECTION_PLATFORM_MAINTENANCE:
msg = _("Platform Maintenance Parameter '%s' is required." %
parameter.name)
raise wsme.exc.ClientSideError(msg)
# delete parameter from sysinv database
pecan.request.dbapi.service_parameter_destroy_uuid(uuid)
# if the parameter being deleted has an associated K8s configmap,
# delete it using the K8s API.
if parameter.section in k8s_volumes_sections:
delete_k8s_configmap(parameter.as_dict(), self.kube_operator)
try:
# Pass name to update_service_config only in case the parameter is the Intel
# NIC driver version
name = None
if parameter.section == constants.SERVICE_PARAM_SECTION_PLATFORM_CONFIG and \
parameter.name == constants.SERVICE_PARAM_NAME_PLAT_CONFIG_INTEL_NIC_DRIVER_VERSION:
name = parameter.name
pecan.request.rpcapi.update_service_config(
pecan.request.context,
parameter.service,
section=parameter.section,
name=name)
except rpc_common.RemoteError as e:
# rollback destroy service parameter
try:
parameter = parameter.as_dict()
pecan.request.dbapi.service_parameter_create(parameter)
LOG.warn(_("Rollback service parameter destroy: "
"create parameter with values={}".format(parameter)))
# rollback parameter has a different uuid
except exception.SysinvException:
pass
raise wsme.exc.ClientSideError(str(e.value))
@staticmethod
def _service_parameter_apply_semantic_check_mtce():
"""Semantic checks for the Platform Maintenance Service Type """
hbs_failure_threshold = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_PLATFORM,
section=constants.SERVICE_PARAM_SECTION_PLATFORM_MAINTENANCE,
name=constants.SERVICE_PARAM_PLAT_MTCE_HBS_FAILURE_THRESHOLD)
hbs_degrade_threshold = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_PLATFORM,
section=constants.SERVICE_PARAM_SECTION_PLATFORM_MAINTENANCE,
name=constants.SERVICE_PARAM_PLAT_MTCE_HBS_DEGRADE_THRESHOLD)
if int(hbs_degrade_threshold.value) >= int(hbs_failure_threshold.value):
msg = _("Unable to apply service parameters. "
"Service parameter '%s' should be greater than '%s' "
% (
constants.SERVICE_PARAM_PLAT_MTCE_HBS_FAILURE_THRESHOLD,
constants.SERVICE_PARAM_PLAT_MTCE_HBS_DEGRADE_THRESHOLD
))
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _service_parameter_apply_semantic_check_http():
"""Semantic checks for the HTTP Service Type """
# check if a patching operation in progress
fm = fm_api.FaultAPIs()
alarms = fm.get_faults_by_id(fm_constants.
FM_ALARM_ID_PATCH_IN_PROGRESS)
if alarms is not None:
msg = _("Unable to apply %s service parameters. "
"A patching operation is in progress."
% constants.SERVICE_TYPE_HTTP)
raise wsme.exc.ClientSideError(msg)
# check if a device image update operation is in progress
alarms = fm.get_faults_by_id(fm_constants.
FM_ALARM_ID_DEVICE_IMAGE_UPDATE_IN_PROGRESS)
if alarms is not None:
msg = _("Unable to apply %s service parameters. "
"A device image update operation is in progress. "
"Please try again later when the operation is complete."
% constants.SERVICE_TYPE_HTTP)
raise wsme.exc.ClientSideError(msg)
# check if all hosts are unlocked/enabled
hosts = pecan.request.dbapi.ihost_get_list()
for host in hosts:
if (host['administrative'] == constants.ADMIN_UNLOCKED and
host['operational'] == constants.OPERATIONAL_ENABLED):
continue
else:
# the host name might be None for a newly discovered host
if not host['hostname']:
host_id = host['uuid']
else:
host_id = host['hostname']
raise wsme.exc.ClientSideError(
_("Host %s must be unlocked and enabled." % host_id))
@staticmethod
def _service_parameter_apply_semantic_check_kubernetes():
"""Semantic checks for the Platform Kubernetes Service Type """
try:
oidc_issuer_url = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_KUBERNETES,
section=constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER,
name=constants.SERVICE_PARAM_NAME_OIDC_ISSUER_URL)
except exception.NotFound:
oidc_issuer_url = None
try:
oidc_client_id = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_KUBERNETES,
section=constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER,
name=constants.SERVICE_PARAM_NAME_OIDC_CLIENT_ID)
except exception.NotFound:
oidc_client_id = None
try:
oidc_username_claim = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_KUBERNETES,
section=constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER,
name=constants.SERVICE_PARAM_NAME_OIDC_USERNAME_CLAIM)
except exception.NotFound:
oidc_username_claim = None
try:
oidc_groups_claim = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_KUBERNETES,
section=constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER,
name=constants.SERVICE_PARAM_NAME_OIDC_GROUPS_CLAIM)
except exception.NotFound:
oidc_groups_claim = None
if not ((not oidc_issuer_url and not oidc_client_id and
not oidc_username_claim and not oidc_groups_claim) or
(oidc_issuer_url and oidc_client_id and
oidc_username_claim and not oidc_groups_claim) or
(oidc_issuer_url and oidc_client_id and
oidc_username_claim and oidc_groups_claim)):
msg = _("Unable to apply service parameters. Please choose one of "
"the valid Kubernetes OIDC parameter setups: (None) or "
"(oidc-issuer-url, oidc-client-id, oidc-username-claim) or "
"(the previous 3 plus oidc-groups-claim)")
raise wsme.exc.ClientSideError(msg)
def _service_parameter_apply_semantic_check(self, service):
"""Semantic checks for the service-parameter-apply command """
# Check if all the mandatory parameters have been configured
for section, schema in service_parameter.SERVICE_PARAMETER_SCHEMA[service].items():
mandatory = schema.get(service_parameter.SERVICE_PARAM_MANDATORY, [])
for name in mandatory:
try:
pecan.request.dbapi.service_parameter_get_one(
service=service, section=section, name=name)
except exception.NotFound:
msg = _("Unable to apply service parameters. "
"Missing service parameter '%s' for service '%s' "
"in section '%s'." % (name, service, section))
raise wsme.exc.ClientSideError(msg)
# Apply service specific semantic checks
if service == constants.SERVICE_TYPE_PLATFORM:
self._service_parameter_apply_semantic_check_mtce()
elif service == constants.SERVICE_TYPE_HTTP:
self._service_parameter_apply_semantic_check_http()
elif service == constants.SERVICE_TYPE_KUBERNETES:
self._service_parameter_apply_semantic_check_kubernetes()
elif service == constants.SERVICE_TYPE_PTP:
msg = _("%s service is deprecated" % service)
raise wsme.exc.ClientSideError(msg)
def _get_service(self, body):
service = body.get('service') or ""
if not service:
raise wsme.exc.ClientSideError("Unspecified service name")
if body['service'] not in service_parameter.SERVICE_PARAMETER_SCHEMA:
msg = _("Invalid service name %s." % body['service'])
raise wsme.exc.ClientSideError(msg)
return service
def _get_section(self, body):
section = body.get('section') or ""
return section
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose('json', body=six.text_type)
def apply(self, body):
""" Apply the service parameters."""
service = self._get_service(body)
section = self._get_section(body)
self._service_parameter_apply_semantic_check(service)
try:
pecan.request.rpcapi.update_service_config(
pecan.request.context, service, section=section, do_apply=True)
except rpc_common.RemoteError as e:
raise wsme.exc.ClientSideError(str(e.value))
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.exception(e)
def enforce_policy(self, method_name, request):
"""Check policy rules for each action of this controller."""
context_dict = request.context.to_dict()
if method_name == "apply":
policy.authorize(sp_policy.POLICY_ROOT % "apply", {}, context_dict)
elif method_name == "delete":
policy.authorize(sp_policy.POLICY_ROOT % "delete", {}, context_dict)
elif method_name in ["get_all", "get_one"]:
policy.authorize(sp_policy.POLICY_ROOT % "get", {}, context_dict)
elif method_name == "patch":
policy.authorize(sp_policy.POLICY_ROOT % "modify", {}, context_dict)
elif method_name == "post":
policy.authorize(sp_policy.POLICY_ROOT % "add", {}, context_dict)
else:
raise exception.PolicyNotFound()