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

1353 lines
60 KiB
Python

# Copyright (c) 2015-2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
#
import copy
import netaddr
import pecan
from fm_api import constants as fm_constants
from fm_api import fm_api
from pecan import rest
import six
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from six.moves.urllib.parse import urlparse
from sysinv.api.controllers.v1 import address_pool
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 import objects
from sysinv.common import constants
from sysinv.common import service_parameter
from sysinv.common import exception
from sysinv.common import utils as cutils
from sysinv.openstack.common import log
from sysinv.openstack.common import excutils
from sysinv.openstack.common.gettextutils import _
from sysinv.common.storage_backend_conf import StorageBackendConfig
from sysinv.openstack.common.rpc import common as rpc_common
LOG = log.getLogger(__name__)
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 = 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
# Add additional hpe3par backends
for i in range(2, constants.SERVICE_PARAM_MAX_HPE3PAR + 1):
section = "{0}{1}".format(constants.SERVICE_PARAM_SECTION_CINDER_HPE3PAR, i)
service_parameter.SERVICE_PARAMETER_SCHEMA[constants.SERVICE_TYPE_CINDER][section] = {
service_parameter.SERVICE_PARAM_MANDATORY:
service_parameter.CINDER_HPE3PAR_PARAMETER_MANDATORY,
service_parameter.SERVICE_PARAM_PROTECTED:
service_parameter.CINDER_HPE3PAR_PARAMETER_PROTECTED,
service_parameter.SERVICE_PARAM_OPTIONAL:
service_parameter.CINDER_HPE3PAR_PARAMETER_OPTIONAL,
service_parameter.SERVICE_PARAM_VALIDATOR:
service_parameter.CINDER_HPE3PAR_PARAMETER_VALIDATOR,
service_parameter.SERVICE_PARAM_RESOURCE:
service_parameter.CINDER_HPE3PAR_PARAMETER_RESOURCE,
}
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)
# filter out desired and applied parameters; they are used to keep
# track of updates between two consecutive apply actions
parms = [p for p in parms if not
p.service == constants.SERVICE_TYPE_CEPH]
# filter out cinder state
parms = [p for p in parms if not (
p.service == constants.SERVICE_TYPE_CINDER and (
p.section == constants.SERVICE_PARAM_SECTION_CINDER_EMC_VNX_STATE or
p.section == constants.SERVICE_PARAM_SECTION_CINDER_HPE3PAR_STATE or
p.section == constants.SERVICE_PARAM_SECTION_CINDER_HPELEFTHAND_STATE))]
# filter out cinder multipath state
parms = [p for p in parms if not(
p.service == constants.SERVICE_TYPE_CINDER and
p.section == constants.SERVICE_PARAM_SECTION_CINDER_DEFAULT and
p.name == constants.SERVICE_PARAM_CINDER_DEFAULT_MULTIPATH_STATE)]
# filter out firewall_rules_id
parms = [p for p in parms if not (
p.service == constants.SERVICE_TYPE_PLATFORM and p.section ==
constants.SERVICE_PARAM_SECTION_PLATFORM_SYSINV and p.name ==
constants.SERVICE_PARAM_NAME_SYSINV_FIREWALL_RULES_ID)]
# 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=[], marker=None, limit=None,
sort_key='id', sort_dir='asc'):
"""Retrieve a list of service parameters."""
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_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']
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 not in parameters:
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 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 service == constants.SERVICE_TYPE_CEPH:
if not StorageBackendConfig.has_backend_configured(
pecan.request.dbapi, constants.CINDER_BACKEND_CEPH):
msg = _("Ceph backend is required.")
raise wsme.exc.ClientSideError(msg)
if len(parameters) > 1:
msg = _("Cannot specify multiple parameters with custom resource.")
raise wsme.exc.CommandError(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)
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."))
if service == constants.SERVICE_TYPE_CEPH:
if not StorageBackendConfig.has_backend_configured(
pecan.request.dbapi, constants.CINDER_BACKEND_CEPH):
msg = _("Ceph backend is required.")
raise wsme.exc.ClientSideError(msg)
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:
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:
pecan.request.rpcapi.update_service_config(
pecan.request.context, service)
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'])
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.service == constants.SERVICE_TYPE_CEPH:
if not StorageBackendConfig.has_backend_configured(
pecan.request.dbapi, constants.CINDER_BACKEND_CEPH):
msg = _("Ceph backend is required.")
raise wsme.exc.ClientSideError(msg)
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)
if parameter['service'] == constants.SERVICE_TYPE_CINDER:
if (parameter['name'] ==
constants.SERVICE_PARAM_CINDER_EMC_VNX_ENABLED):
if (parameter['value'].lower() == 'false' and
old_parameter['value'].lower() == 'true'):
if not pecan.request.rpcapi.validate_emc_removal(
pecan.request.context):
msg = _(
"Unable to modify service parameter. Can not "
"disable %s while in use"
% constants.SERVICE_PARAM_SECTION_CINDER_EMC_VNX)
raise wsme.exc.ClientSideError(msg)
updated_parameter = pecan.request.dbapi.service_parameter_update(
uuid, updates)
try:
pecan.request.rpcapi.update_service_config(
pecan.request.context,
parameter['service'])
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.service == constants.SERVICE_TYPE_CEPH:
if not StorageBackendConfig.has_backend_configured(
pecan.request.dbapi, constants.CINDER_BACKEND_CEPH):
msg = _("Ceph backend is required.")
raise wsme.exc.ClientSideError(msg)
if parameter.service == constants.SERVICE_TYPE_CINDER:
if parameter.name == 'data_san_ip':
msg = _("Parameter '%s' is readonly." % parameter.name)
raise wsme.exc.ClientSideError(msg)
if parameter.section == \
constants.SERVICE_PARAM_SECTION_PLATFORM_MAINTENANCE:
msg = _("Platform Maintenance Parameter '%s' is required." %
parameter.name)
raise wsme.exc.ClientSideError(msg)
pecan.request.dbapi.service_parameter_destroy_uuid(uuid)
try:
pecan.request.rpcapi.update_service_config(
pecan.request.context,
parameter.service)
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_identity():
""" Perform checks for the Identity Service Type."""
identity_driver = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_IDENTITY,
section=constants.SERVICE_PARAM_SECTION_IDENTITY_IDENTITY,
name=constants.SERVICE_PARAM_IDENTITY_DRIVER)
# Check that the LDAP URL is specified if the identity backend is LDAP
if (identity_driver.value ==
constants.SERVICE_PARAM_IDENTITY_IDENTITY_DRIVER_LDAP):
try:
pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_IDENTITY,
section=constants.SERVICE_PARAM_SECTION_IDENTITY_LDAP,
name=service_parameter.SERVICE_PARAM_IDENTITY_LDAP_URL)
except exception.NotFound:
msg = _("Unable to apply service parameters. "
"Missing service parameter '%s' for service '%s' "
"in section '%s'." % (
service_parameter.SERVICE_PARAM_IDENTITY_LDAP_URL,
constants.SERVICE_TYPE_IDENTITY,
constants.SERVICE_PARAM_SECTION_IDENTITY_LDAP))
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _service_parameter_apply_semantic_check_cinder_default():
"""Semantic checks for the Cinder Service Type: DEFAULT parameters """
try:
volume_type = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=constants.SERVICE_PARAM_SECTION_CINDER_DEFAULT,
name=constants.SERVICE_PARAM_CINDER_DEFAULT_VOLUME_TYPE)
except exception.MultipleResults:
msg = (_('Unable to apply service parameters. Multiple parameters '
'found for %s/%s/%s. Ensure only one parameter is '
'provided.') % (
constants.SERVICE_TYPE_CINDER,
constants.SERVICE_PARAM_SECTION_CINDER_DEFAULT,
constants.SERVICE_PARAM_CINDER_DEFAULT_VOLUME_TYPE))
raise wsme.exc.ClientSideError(msg)
except exception.NotFound:
# not required to be set
volume_type = None
if volume_type:
try:
volume_types = pecan.request.rpcapi.get_cinder_volume_type_names(
pecan.request.context)
except rpc_common.RemoteError as e:
raise wsme.exc.ClientSideError(str(e.value))
if volume_type.value not in volume_types:
msg = (_('Unable to apply service parameters. Cannot set "%s" '
'to value "%s". This is not a valid cinder volume '
'type. Acceptable values are: [%s].') % (
constants.SERVICE_PARAM_CINDER_DEFAULT_VOLUME_TYPE,
volume_type.value, ','.join(volume_types)))
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _service_parameter_apply_semantic_check_cinder_emc_vnx():
"""Semantic checks for the Cinder Service Type: EMC VNX backend """
feature_enabled = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=constants.SERVICE_PARAM_SECTION_CINDER_EMC_VNX,
name=constants.SERVICE_PARAM_CINDER_EMC_VNX_ENABLED)
if feature_enabled.value.lower() == 'true':
for name in service_parameter.CINDER_EMC_VNX_PARAMETER_REQUIRED_ON_FEATURE_ENABLED:
try:
pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=constants.SERVICE_PARAM_SECTION_CINDER_EMC_VNX,
name=name)
except exception.NotFound:
msg = _("Unable to apply service parameters. "
"Missing service parameter '%s' for service '%s' "
"in section '%s'." % (name,
constants.SERVICE_TYPE_CINDER,
constants.SERVICE_PARAM_SECTION_CINDER_EMC_VNX))
raise wsme.exc.ClientSideError(msg)
else:
if not pecan.request.rpcapi.validate_emc_removal(
pecan.request.context):
msg = _("Unable to apply service parameters. Can not disable "
"%s while in use. Remove any EMC volumes."
% constants.SERVICE_PARAM_SECTION_CINDER_EMC_VNX)
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _emc_vnx_ip_addresses_reservation():
"""Reserve the provided IP addresses """
# To keep the EMC IP addresses information between service_parameter
# db and addresses db in-sync. So that sysinv won't assign these IP
# addresses to someone else
#
# service_parameter | addresses
# ------------------------------------------------------------
# san_ip | controller-emc-vnx-san-ip-<network-type>
# (user provides) |
# ------------------------------------------------------------
# san_secondary_ip | controller-emc-vnx-san-
# (user provides) | secondary-ip-<network-type>
# ------------------------------------------------------------
# data_san_ip | controller-emc-vnx-data-san-ip-
# | <network-type> (generated internally)
# ------------------------------------------------------------
#
# controller-emc-vnx-san-ip and controller-emc-vnx-san-secondary-ip
# are in 'control_network' network and controller-emc-vnx-data-san-ip
# is in 'data_network' network.
feature_enabled = service_parameter._emc_vnx_get_param_from_name(
constants.SERVICE_PARAM_CINDER_EMC_VNX_ENABLED)
data_san_ip_param = service_parameter._emc_vnx_get_param_from_name(
service_parameter.CINDER_EMC_VNX_DATA_SAN_IP)
prev_data_san_ip_db = service_parameter._emc_vnx_get_address_db(
service_parameter.CINDER_EMC_VNX_DATA_SAN_IP,
control_network=False)[0]
# Always remove the reserved control IP addresses out of network
# because of the following scenarios:
# * feature turned off need to delete
# * user modifies 'control_network' parameter from e.g. infra to oam
# And later will be re-added if neccessary
prev_san_ip_db, prev_control_network_type = \
service_parameter._emc_vnx_get_address_db(
service_parameter.CINDER_EMC_VNX_SAN_IP, control_network=True)
service_parameter._emc_vnx_db_destroy_address(prev_san_ip_db)
prev_san_secondary_ip_db = service_parameter._emc_vnx_get_address_db(
service_parameter.CINDER_EMC_VNX_SAN_SECONDARY_IP,
network_type=prev_control_network_type)[0]
service_parameter._emc_vnx_db_destroy_address(prev_san_secondary_ip_db)
# Enabling emc_vnx feature, we need to
if feature_enabled.value.lower() == 'true':
# Control IP, user will provide san_ip and san_secondary_ip
# (optional). Here we just save these IP addresses into
# 'control_network' network
control_network_param = \
service_parameter._emc_vnx_get_param_from_name(
service_parameter.CINDER_EMC_VNX_CONTROL_NETWORK)
# Don't reserve address for oam network
if control_network_param.value != constants.NETWORK_TYPE_OAM:
try:
pool_uuid = pecan.request.dbapi.network_get_by_type(
control_network_param.value).pool_uuid
pool = pecan.request.dbapi.address_pool_get(pool_uuid)
service_parameter._emc_vnx_save_address_from_param(
service_parameter.CINDER_EMC_VNX_SAN_IP,
control_network_param.value, pool)
service_parameter._emc_vnx_save_address_from_param(
service_parameter.CINDER_EMC_VNX_SAN_SECONDARY_IP,
control_network_param.value, pool)
except exception.NetworkTypeNotFound:
msg = _("Unable to apply service parameters. "
"Cannot find specified EMC control "
"network '%s'" % control_network_param.value)
raise wsme.exc.ClientSideError(msg)
except exception.AddressPoolNotFound:
msg = _("Unable to apply service parameters. "
"Network '%s' has no address pool associated" %
control_network_param.value)
raise wsme.exc.ClientSideError(msg)
# Data IP, we need to assign an IP address out of 'data_network'
# network set it to readonly service parameter 'data-san-ip'.
#
# User can change the data_network (e.g from infra to mgnt)
# which means we need to remove the existing and assign new IP
# from new data_network
data_network_param = service_parameter._emc_vnx_get_param_from_name(
service_parameter.CINDER_EMC_VNX_DATA_NETWORK)
try:
data_network_db = pecan.request.dbapi.network_get_by_type(
data_network_param.value)
except exception.NetworkTypeNotFound:
msg = _("Unable to apply service parameters. "
"Cannot find specified EMC data network '%s'" % (
data_network_param.value))
raise wsme.exc.ClientSideError(msg)
# If addressses db already contain the address and new request
# come in with different network we first need to delete the
# existing one
if (prev_data_san_ip_db and prev_data_san_ip_db.pool_uuid !=
data_network_db.pool_uuid):
service_parameter._emc_vnx_destroy_data_san_address(
data_san_ip_param, prev_data_san_ip_db)
data_san_ip_param = None
if not data_san_ip_param:
try:
assigned_address = (
address_pool.AddressPoolController.assign_address(
None, data_network_db.pool_uuid,
service_parameter._emc_vnx_format_address_name_db(
service_parameter.CINDER_EMC_VNX_DATA_SAN_IP,
data_network_param.value)))
pecan.request.dbapi.service_parameter_create({
'service': constants.SERVICE_TYPE_CINDER,
'section':
constants.SERVICE_PARAM_SECTION_CINDER_EMC_VNX,
'name': service_parameter.CINDER_EMC_VNX_DATA_SAN_IP,
'value': assigned_address.address})
except exception.AddressPoolExhausted:
msg = _("Unable to apply service parameters. "
"The address pool '%s' in Data EMC network '%s' "
"is full" % (data_network_db.pool_uuid,
data_network_param.value))
raise wsme.exc.ClientSideError(msg)
except exception.AddressNotFound:
msg = _("Unable to apply service parameters. "
"Cannot add generated '%s' address into "
"pool '%s'" % (service_parameter.CINDER_EMC_VNX_DATA_SAN_IP,
data_network_db.pool_uuid))
raise wsme.exc.ClientSideError(msg)
except exception.ServiceParameterAlreadyExists:
# If can not add assigned data san ip address into
# service parameter then need to release it too
service_parameter._emc_vnx_db_destroy_address(
assigned_address)
msg = _("Unable to apply service parameters. "
"Cannot add generated '%s' address '%s' "
"into service parameter '%s'" % (
service_parameter.CINDER_EMC_VNX_DATA_SAN_IP,
assigned_address.address,
data_san_ip_param.value))
raise wsme.exc.ClientSideError(msg)
else:
# Need to remove the reserved Data IP addresses out of network
service_parameter._emc_vnx_destroy_data_san_address(
data_san_ip_param, prev_data_san_ip_db)
@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 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))
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_IDENTITY:
self._service_parameter_apply_semantic_check_identity()
if service == constants.SERVICE_TYPE_CINDER:
# Make sure one of the internal cinder configs is enabled so that
# we know cinder is operational in this region
if not StorageBackendConfig.is_service_enabled(pecan.request.dbapi,
constants.SB_SVC_CINDER,
filter_shared=True):
msg = _("Cannot apply Cinder configuration. Cinder is not "
"currently enabled on either the %s or %s backends."
% (constants.SB_TYPE_LVM, constants.SB_TYPE_CEPH))
raise wsme.exc.ClientSideError(msg)
self._service_parameter_apply_semantic_check_cinder_default()
self._service_parameter_apply_semantic_check_cinder_emc_vnx()
self._emc_vnx_ip_addresses_reservation()
self._service_parameter_apply_semantic_check_cinder_hpe3par(constants.SERVICE_PARAM_SECTION_CINDER_HPE3PAR)
self._hpe3par_reserve_ip_addresses(constants.SERVICE_PARAM_SECTION_CINDER_HPE3PAR)
for i in range(2, constants.SERVICE_PARAM_MAX_HPE3PAR + 1):
section = "{0}{1}".format(constants.SERVICE_PARAM_SECTION_CINDER_HPE3PAR, i)
self._service_parameter_apply_semantic_check_cinder_hpe3par(section)
self._hpe3par_reserve_ip_addresses(section)
self._service_parameter_apply_semantic_check_cinder_hpelefthand()
self._hpelefthand_reserve_ip_addresses()
if service == constants.SERVICE_TYPE_PLATFORM:
self._service_parameter_apply_semantic_check_mtce()
if service == constants.SERVICE_TYPE_HTTP:
self._service_parameter_apply_semantic_check_http()
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
@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)
self._service_parameter_apply_semantic_check(service)
try:
pecan.request.rpcapi.update_service_config(
pecan.request.context, service, 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)
@staticmethod
def _hpe3par_reserve_ip_addresses(section):
"""
We need to keep the address information between service_parameter
db and addresses db in-sync so that sysinv won't assign the IP
addresses to someone else.
Create an entry in the addresses db for each service parameter.
Service Parameter | Address DB Entry Name
---------------------------------------------------------------
hpe3par_api_url | <section>-api-ip
---------------------------------------------------------------
hpe3par_iscsi_ips | <section>-iscsi-ip<n>
---------------------------------------------------------------
san_ip | <section>-san-ip
---------------------------------------------------------------
"""
#
# Remove current addresses. They will be added below if the
# feature is enabled.
#
name = section + "-api-ip"
try:
addr = pecan.request.dbapi.address_get_by_name(name)
LOG.debug("Removing address %s" % name)
pecan.request.dbapi.address_destroy(addr.uuid)
except exception.AddressNotFoundByName:
pass
i = 0
while True:
name = section + "-iscsi-ip" + str(i)
try:
addr = pecan.request.dbapi.address_get_by_name(name)
LOG.debug("Removing address %s" % name)
pecan.request.dbapi.address_destroy(addr.uuid)
i += 1
except exception.AddressNotFoundByName:
break
name = section + "-san-ip"
try:
addr = pecan.request.dbapi.address_get_by_name(name)
LOG.debug("Removing address %s" % name)
pecan.request.dbapi.address_destroy(addr.uuid)
except exception.AddressNotFoundByName:
pass
enabled = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=section,
name="enabled")
if enabled.value.lower() == 'false':
return
#
# Add the hpe3par-api-ip address.
#
api_url = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=section,
name="hpe3par_api_url")
url = urlparse(api_url.value)
ip = netaddr.IPAddress(url.hostname)
pool = service_parameter._get_network_pool_from_ip_address(ip, service_parameter.HPE_DATA_NETWORKS)
#
# Is the address in one of the supported network pools? If so, reserve it.
#
if pool is not None:
try:
name = section + "-api-ip"
address = {'address': str(ip),
'prefix': pool['prefix'],
'family': pool['family'],
'enable_dad': constants.IP_DAD_STATES[pool['family']],
'address_pool_id': pool['id'],
'interface_id': None,
'name': name}
LOG.debug("Reserving address %s" % name)
pecan.request.dbapi.address_create(address)
except exception.AddressAlreadyExists:
msg = _("Unable to apply service parameters. "
"Unable to save address '%s' ('%s') into "
"pool '%s'" % (name, str(ip), pool['name']))
raise wsme.exc.ClientSideError(msg)
#
# Add the hpe3par-iscsi-ip addresses.
#
iscsi_ips = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=constants.SERVICE_PARAM_SECTION_CINDER_HPE3PAR,
name="hpe3par_iscsi_ips")
addrs = iscsi_ips.value.split(',')
i = 0
for addr in addrs:
ipstr = addr.split(':')
ip = netaddr.IPAddress(ipstr[0])
pool = service_parameter._get_network_pool_from_ip_address(ip, service_parameter.HPE_DATA_NETWORKS)
#
# Is the address in one of the supported network pools? If so, reserve it.
#
if pool is not None:
try:
name = section + "-iscsi-ip" + str(i)
address = {'address': str(ip),
'prefix': pool['prefix'],
'family': pool['family'],
'enable_dad': constants.IP_DAD_STATES[pool['family']],
'address_pool_id': pool['id'],
'interface_id': None,
'name': name}
LOG.debug("Reserving address %s" % name)
pecan.request.dbapi.address_create(address)
except exception.AddressAlreadyExists:
msg = _("Unable to apply service parameters. "
"Unable to save address '%s' ('%s') into "
"pool '%s'" % (name, str(ip), pool['name']))
raise wsme.exc.ClientSideError(msg)
i += 1
#
# Optionally add the hpe3par-san-ip address.
#
try:
san_ip = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=section,
name="san_ip")
except exception.NotFound:
return
ip = netaddr.IPAddress(san_ip.value)
pool = service_parameter._get_network_pool_from_ip_address(ip, service_parameter.HPE_DATA_NETWORKS)
#
# Is the address in one of the supported network pools? If so, reserve it.
#
if pool is not None:
try:
name = section + "-san-ip"
address = {'address': str(ip),
'prefix': pool['prefix'],
'family': pool['family'],
'enable_dad': constants.IP_DAD_STATES[pool['family']],
'address_pool_id': pool['id'],
'interface_id': None,
'name': name}
LOG.debug("Reserving address %s" % name)
pecan.request.dbapi.address_create(address)
except exception.AddressAlreadyExists:
msg = _("Unable to apply service parameters. "
"Unable to save address '%s' ('%s') into "
"pool '%s'" % (name, str(ip), pool['name']))
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _hpelefthand_reserve_ip_addresses():
"""
We need to keep the address information between service_parameter
db and addresses db in-sync so that sysinv won't assign the IP
addresses to someone else.
Create an entry in the addresses db for each service parameter.
Service Parameter | Address DB Entry Name
---------------------------------------------------------------
hpelefthand_api_url | hpelefthand-api-ip
---------------------------------------------------------------
"""
#
# Remove current addresses. They will be added below if the
# feature is enabled.
#
name = "hpelefthand-api-ip"
try:
addr = pecan.request.dbapi.address_get_by_name(name)
LOG.debug("Removing address %s" % name)
pecan.request.dbapi.address_destroy(addr.uuid)
except exception.AddressNotFoundByName:
pass
enabled = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=constants.SERVICE_PARAM_SECTION_CINDER_HPELEFTHAND,
name="enabled")
if enabled.value.lower() == 'false':
return
#
# Add the hplefthand-api-ip address.
#
api_url = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=constants.SERVICE_PARAM_SECTION_CINDER_HPELEFTHAND,
name="hpelefthand_api_url")
url = urlparse(api_url.value)
ip = netaddr.IPAddress(url.hostname)
pool = service_parameter._get_network_pool_from_ip_address(ip, service_parameter.HPE_DATA_NETWORKS)
if pool is not None:
try:
address = {'address': str(ip),
'prefix': pool['prefix'],
'family': pool['family'],
'enable_dad': constants.IP_DAD_STATES[pool['family']],
'address_pool_id': pool['id'],
'interface_id': None,
'name': name}
LOG.debug("Reserving address %s" % name)
pecan.request.dbapi.address_create(address)
except exception.AddressAlreadyExists:
msg = _("Unable to apply service parameters. "
"Unable to save address '%s' ('%s') into "
"pool '%s'" % (name, str(ip), pool['name']))
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _service_parameter_apply_semantic_check_cinder_hpe3par(section):
"""Semantic checks for the Cinder Service Type """
feature_enabled = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=section,
name=constants.SERVICE_PARAM_CINDER_SAN_CHANGE_STATUS_ENABLED)
if feature_enabled.value.lower() == 'true':
# Client library installed? If not fail.
if not service_parameter._rpm_pkg_is_installed('python-3parclient'):
msg = _("Unable to apply service parameters. "
"Missing client library python-3parclient.")
raise wsme.exc.ClientSideError(msg)
for name in service_parameter.CINDER_HPE3PAR_PARAMETER_REQUIRED:
try:
pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=section,
name=name)
except exception.NotFound:
msg = _("Unable to apply service parameters. "
"Missing service parameter '%s' for service '%s' "
"in section '%s'." % (name,
constants.SERVICE_TYPE_CINDER,
section))
raise wsme.exc.ClientSideError(msg)
else:
if not pecan.request.rpcapi.validate_hpe3par_removal(
pecan.request.context, section):
msg = _("Unable to apply service parameters. Can not disable "
"%s while in use. Remove any HPE3PAR volumes."
% section)
raise wsme.exc.ClientSideError(msg)
@staticmethod
def _service_parameter_apply_semantic_check_cinder_hpelefthand():
"""Semantic checks for the Cinder Service Type """
feature_enabled = pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=constants.SERVICE_PARAM_SECTION_CINDER_HPELEFTHAND,
name=constants.SERVICE_PARAM_CINDER_SAN_CHANGE_STATUS_ENABLED)
if feature_enabled.value.lower() == 'true':
# Client library installed? If not fail.
if not service_parameter._rpm_pkg_is_installed('python-lefthandclient'):
msg = _("Unable to apply service parameters. "
"Missing client library python-lefthandclient.")
raise wsme.exc.ClientSideError(msg)
for name in service_parameter.CINDER_HPELEFTHAND_PARAMETER_REQUIRED:
try:
pecan.request.dbapi.service_parameter_get_one(
service=constants.SERVICE_TYPE_CINDER,
section=constants.SERVICE_PARAM_SECTION_CINDER_HPELEFTHAND,
name=name)
except exception.NotFound:
msg = _("Unable to apply service parameters. "
"Missing service parameter '%s' for service '%s' "
"in section '%s'." % (name,
constants.SERVICE_TYPE_CINDER,
constants.SERVICE_PARAM_SECTION_CINDER_HPELEFTHAND))
raise wsme.exc.ClientSideError(msg)
else:
if not pecan.request.rpcapi.validate_hpelefthand_removal(
pecan.request.context):
msg = _("Unable to apply service parameters. Can not disable "
"%s while in use. Remove any HPELEFTHAND volumes."
% constants.SERVICE_PARAM_SECTION_CINDER_HPELEFTHAND)
raise wsme.exc.ClientSideError(msg)