743a292630
1. Fixes the traceback raised by sysinv conductor when reprovisioning external ceph backend using same ceph.conf naming as old backend 2. Allows ceph config file to be overwritten in the /opt/platform/config/version/ceph-config directory 3. In the original design, a new directory, /opt/extension/ceph, would be used to store external ceph config files. This directory is not actually needed. This commit removes the code in drbd.pp that creates this directory. 4. In storage_ceph_external.py, use uuid instead of isystem_uuid. Story: 2002820 Task: 22737 Change-Id: Ic9bbfd5859b2ce8a02b6940965db16b78ce854ae Signed-off-by: Irina Mihai <irina.mihai@windriver.com>
590 lines
22 KiB
Python
590 lines
22 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
#
|
|
# Copyright 2017 UnitedStack Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
#
|
|
#
|
|
# Copyright (c) 2018 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
import jsonpatch
|
|
import copy
|
|
import os
|
|
from oslo_serialization import jsonutils
|
|
|
|
import pecan
|
|
from pecan import rest
|
|
from pecan import expose
|
|
import six
|
|
|
|
import wsme
|
|
from wsme import types as wtypes
|
|
import wsmeext.pecan as wsme_pecan
|
|
|
|
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.utils import SBApiHelper as api_helper
|
|
from sysinv.common import constants
|
|
from sysinv.common import exception
|
|
from sysinv.common import utils as cutils
|
|
from sysinv import objects
|
|
from sysinv.openstack.common import log
|
|
from sysinv.openstack.common import uuidutils
|
|
from sysinv.openstack.common.gettextutils import _
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
HIERA_DATA = {
|
|
'backend': [],
|
|
constants.SB_SVC_CINDER: ['cinder_pool'],
|
|
constants.SB_SVC_GLANCE: ['glance_pool'],
|
|
constants.SB_SVC_NOVA: ['ephemeral_pool']
|
|
}
|
|
|
|
|
|
class StorageCephExternalPatchType(types.JsonPatchType):
|
|
@staticmethod
|
|
def mandatory_attrs():
|
|
return []
|
|
|
|
|
|
class StorageCephExternal(base.APIBase):
|
|
"""API representation of an external ceph storage.
|
|
|
|
This class enforces type checking and value constraints, and converts
|
|
between the internal object model and the API representation of
|
|
an external ceph storage.
|
|
"""
|
|
|
|
uuid = types.uuid
|
|
"Unique UUID for this external storage backend."
|
|
|
|
links = [link.Link]
|
|
"A list containing a self link and associated storage backend links."
|
|
|
|
created_at = wtypes.datetime.datetime
|
|
updated_at = wtypes.datetime.datetime
|
|
|
|
ceph_conf = wtypes.text
|
|
"Path to the configuration file for the external ceph cluster."
|
|
|
|
# Inherited attributes from the base class
|
|
backend = wtypes.text
|
|
"Represents the storage backend (file, lvm, ceph, ceph_external or external)."
|
|
|
|
name = wtypes.text
|
|
"The name of the backend (to differentiate between multiple common backends)."
|
|
|
|
state = wtypes.text
|
|
"The state of the backend. It can be configured or configuring."
|
|
|
|
task = wtypes.text
|
|
"Current task of the corresponding cinder backend."
|
|
|
|
services = wtypes.text
|
|
"The openstack services that are supported by this storage backend."
|
|
|
|
capabilities = {wtypes.text: utils.ValidTypes(wtypes.text,
|
|
six.integer_types)}
|
|
"Meta data for the storage backend"
|
|
|
|
# Confirmation parameter [API-only field]
|
|
confirmed = types.boolean
|
|
"Represent confirmation that the backend operation should proceed"
|
|
|
|
def __init__(self, **kwargs):
|
|
defaults = {'uuid': uuidutils.generate_uuid(),
|
|
'state': constants.SB_STATE_CONFIGURING,
|
|
'task': constants.SB_TASK_NONE,
|
|
'capabilities': {},
|
|
'services': None,
|
|
'confirmed': False,
|
|
'ceph_conf': None}
|
|
|
|
self.fields = objects.storage_ceph_external.fields.keys()
|
|
|
|
# 'confirmed' is not part of objects.storage_backend.fields
|
|
# (it's an API-only attribute)
|
|
self.fields.append('confirmed')
|
|
|
|
# Set the value for any of the field
|
|
for k in self.fields:
|
|
setattr(self, k, kwargs.get(k, defaults.get(k)))
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_storage_ceph_external, expand=True):
|
|
|
|
stor_ceph_external = StorageCephExternal(**rpc_storage_ceph_external.as_dict())
|
|
if not expand:
|
|
stor_ceph_external.unset_fields_except(['uuid',
|
|
'created_at',
|
|
'updated_at',
|
|
'isystem_uuid',
|
|
'backend',
|
|
'name',
|
|
'state',
|
|
'task',
|
|
'services',
|
|
'capabilities',
|
|
'ceph_conf'])
|
|
|
|
stor_ceph_external.links =\
|
|
[link.Link.make_link('self', pecan.request.host_url,
|
|
'storage_ceph_external',
|
|
stor_ceph_external.uuid),
|
|
link.Link.make_link('bookmark', pecan.request.host_url,
|
|
'storage_ceph_external',
|
|
stor_ceph_external.uuid,
|
|
bookmark=True)]
|
|
|
|
return stor_ceph_external
|
|
|
|
|
|
class StorageCephExternalCollection(collection.Collection):
|
|
"""API representation of a collection of external ceph storage backends."""
|
|
|
|
storage_ceph_external = [StorageCephExternal]
|
|
"A list containing ceph external storage backend objects."
|
|
|
|
def __init__(self, **kwargs):
|
|
self._type = 'storage_ceph_external'
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_storage_ceph_external, limit, url=None,
|
|
expand=False, **kwargs):
|
|
collection = StorageCephExternalCollection()
|
|
collection.storage_ceph_external = \
|
|
[StorageCephExternal.convert_with_links(p, expand)
|
|
for p in rpc_storage_ceph_external]
|
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
|
return collection
|
|
|
|
|
|
LOCK_NAME = 'StorageCephExternalController'
|
|
|
|
|
|
class StorageCephExternalController(rest.RestController):
|
|
"""REST controller for ceph external storage backend."""
|
|
|
|
_custom_actions = {
|
|
'detail': ['GET'],
|
|
'ceph_conf_upload': ['POST']
|
|
}
|
|
|
|
def _get_storage_ceph_external_collection(
|
|
self, marker, limit, sort_key, sort_dir, expand=False,
|
|
resource_url=None):
|
|
|
|
limit = utils.validate_limit(limit)
|
|
sort_dir = utils.validate_sort_dir(sort_dir)
|
|
|
|
marker_obj = None
|
|
if marker:
|
|
marker_obj = objects.storage_ceph_external.get_by_uuid(
|
|
pecan.request.context,
|
|
marker)
|
|
|
|
ceph_external_storage_backends = \
|
|
pecan.request.dbapi.storage_ceph_external_get_list(
|
|
limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir)
|
|
|
|
return StorageCephExternalCollection \
|
|
.convert_with_links(ceph_external_storage_backends,
|
|
limit,
|
|
url=resource_url,
|
|
expand=expand,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
@wsme_pecan.wsexpose(StorageCephExternalCollection, types.uuid, int,
|
|
wtypes.text, wtypes.text)
|
|
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
|
"""Retrieve a list of ceph external storage backends."""
|
|
|
|
return self._get_storage_ceph_external_collection(
|
|
marker, limit, sort_key, sort_dir)
|
|
|
|
@wsme_pecan.wsexpose(StorageCephExternal, types.uuid)
|
|
def get_one(self, storage_ceph_external_uuid):
|
|
"""Retrieve information about the given ceph external storage
|
|
backend.
|
|
"""
|
|
|
|
rpc_storage_ceph_external = objects.storage_ceph_external.get_by_uuid(
|
|
pecan.request.context,
|
|
storage_ceph_external_uuid)
|
|
return StorageCephExternal.convert_with_links(
|
|
rpc_storage_ceph_external)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme_pecan.wsexpose(StorageCephExternal, body=StorageCephExternal)
|
|
def post(self, storage_ceph_external):
|
|
"""Create a new external storage backend."""
|
|
|
|
try:
|
|
storage_ceph_external = storage_ceph_external.as_dict()
|
|
new_storage_ceph_external = _create(storage_ceph_external)
|
|
|
|
except exception.SysinvException as e:
|
|
LOG.exception(e)
|
|
raise wsme.exc.ClientSideError(_("Invalid data: failed to create "
|
|
"a storage_ceph_external record."))
|
|
|
|
return StorageCephExternal.convert_with_links(new_storage_ceph_external)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme.validate(types.uuid, [StorageCephExternalPatchType])
|
|
@wsme_pecan.wsexpose(StorageCephExternal, types.uuid,
|
|
body=[StorageCephExternalPatchType])
|
|
def patch(self, storexternal_uuid, patch):
|
|
"""Update the current external storage configuration."""
|
|
return _patch(storexternal_uuid, patch)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
|
def delete(self, storageexternal_uuid):
|
|
"""Delete a backend."""
|
|
|
|
# return _delete(storageexternal_uuid)
|
|
|
|
@expose('json')
|
|
@cutils.synchronized(LOCK_NAME)
|
|
def ceph_conf_upload(self, file):
|
|
""" Upload Ceph Config file
|
|
"""
|
|
file = pecan.request.POST['file']
|
|
ceph_conf_fn = pecan.request.POST.get('ceph_conf_fn')
|
|
|
|
if ceph_conf_fn == constants.SB_TYPE_CEPH_CONF_FILENAME:
|
|
msg = _("The %s name is reserved for the internally managed Ceph "
|
|
"cluster.\nPlease use a different name and try again." %
|
|
constants.SB_TYPE_CEPH_CONF_FILENAME)
|
|
return dict(success="", error=msg)
|
|
|
|
if not file.filename:
|
|
return dict(success="", error="Error: No file uploaded")
|
|
try:
|
|
file.file.seek(0, os.SEEK_SET)
|
|
file_content = file.file.read()
|
|
|
|
pecan.request.rpcapi.store_ceph_external_config(
|
|
pecan.request.context, file_content, ceph_conf_fn)
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
return dict(
|
|
success="",
|
|
error=e.value)
|
|
|
|
return dict(success="Success: ceph config file is uploaded", error="")
|
|
|
|
|
|
def _discover_and_validate_backend_hiera_data(caps_dict):
|
|
pass
|
|
|
|
|
|
def _check_and_update_services(storage_ceph_ext):
|
|
svcs = api_helper.getListFromServices(storage_ceph_ext)
|
|
|
|
# If glance/nova is already a service on other rbd backend, remove it from there
|
|
check_svcs = [constants.SB_SVC_GLANCE, constants.SB_SVC_NOVA]
|
|
for s in check_svcs:
|
|
if s in svcs:
|
|
sb_list = pecan.request.dbapi.storage_backend_get_list()
|
|
|
|
if sb_list:
|
|
for sb in sb_list:
|
|
if (sb.uuid != storage_ceph_ext.get("uuid", None) and
|
|
sb.backend in [constants.SB_TYPE_CEPH,
|
|
constants.SB_TYPE_CEPH_EXTERNAL] and
|
|
s in sb.get('services')):
|
|
services = api_helper.getListFromServices(sb)
|
|
services.remove(s)
|
|
cap = sb.capabilities
|
|
for k in HIERA_DATA[s]:
|
|
cap.pop(k, None)
|
|
values = {'services': ','.join(services),
|
|
'capabilities': cap, }
|
|
pecan.request.dbapi.storage_backend_update(sb.uuid, values)
|
|
|
|
|
|
def _check_backend_ceph_external(storage_ceph_ext):
|
|
"""Prechecks for adding an external Ceph backend."""
|
|
|
|
# go through the service list and validate
|
|
svcs = api_helper.getListFromServices(storage_ceph_ext)
|
|
|
|
for svc in svcs:
|
|
if svc not in constants.SB_CEPH_EXTERNAL_SVCS_SUPPORTED:
|
|
raise wsme.exc.ClientSideError("Service %s is not supported for the"
|
|
" %s backend" %
|
|
(svc, constants.SB_TYPE_CEPH_EXTERNAL))
|
|
|
|
# check for the backend parameters
|
|
capabilities = storage_ceph_ext.get('capabilities', {})
|
|
|
|
# Discover the latest hiera_data for the supported service
|
|
_discover_and_validate_backend_hiera_data(capabilities)
|
|
|
|
for svc in svcs:
|
|
for k in HIERA_DATA[svc]:
|
|
if not capabilities.get(k, None):
|
|
raise wsme.exc.ClientSideError("Missing required %s service "
|
|
"parameter: %s" % (svc, k))
|
|
|
|
for svc in constants.SB_CEPH_EXTERNAL_SVCS_SUPPORTED:
|
|
for k in HIERA_DATA[svc]:
|
|
if capabilities.get(k, None) and svc not in svcs:
|
|
raise wsme.exc.ClientSideError("Missing required service %s for "
|
|
"parameter: %s" % (svc, k))
|
|
|
|
valid_pars = [i for sublist in HIERA_DATA.values() for i in sublist]
|
|
if len(set(capabilities.keys()) - set(valid_pars)) > 0:
|
|
raise wsme.exc.ClientSideError("Parameter %s is not valid "
|
|
% list(set(capabilities.keys()) - set(valid_pars)))
|
|
|
|
# Check the Ceph configuration file
|
|
ceph_conf_file = storage_ceph_ext.get('ceph_conf')
|
|
if ceph_conf_file:
|
|
if (ceph_conf_file == constants.SB_TYPE_CEPH_CONF_FILENAME):
|
|
msg = _("The %s name is reserved for the internally managed Ceph "
|
|
"cluster.\nPlease use a different name and try again." %
|
|
constants.SB_TYPE_CEPH_CONF_FILENAME)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
else:
|
|
# Raise error if the Ceph configuration file is not provided.
|
|
msg = _("A Ceph configuration file must be provided for provisioning "
|
|
"an external Ceph cluster.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
# If a conf file is specified, make sure the backend's name is not already
|
|
# used / one of the default names for other backends.
|
|
if ceph_conf_file:
|
|
backend_name = storage_ceph_ext.get('name')
|
|
backend_list = pecan.request.dbapi.storage_backend_get_list()
|
|
for backend in backend_list:
|
|
if backend.uuid != storage_ceph_ext.get("uuid", None):
|
|
if backend_name in constants.SB_DEFAULT_NAMES.values():
|
|
msg = _(
|
|
"The \"%s\" name is reserved for internally managed "
|
|
"backends."
|
|
% backend_name)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
if backend.name == backend_name:
|
|
msg = _(
|
|
"The \"%s\" name is already used for another backend." %
|
|
backend_name)
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
|
|
def _apply_ceph_external_backend_changes(op, sb_obj, orig_sb_obj=None):
|
|
if ((op == constants.SB_API_OP_CREATE) or
|
|
(op == constants.SB_API_OP_MODIFY and
|
|
sb_obj.get('ceph_conf') != orig_sb_obj.get('ceph_conf'))):
|
|
|
|
values = {'task': constants.SB_TASK_APPLY_CONFIG_FILE}
|
|
pecan.request.dbapi.storage_ceph_external_update(sb_obj.get('uuid'), values)
|
|
|
|
try:
|
|
pecan.request.rpcapi.distribute_ceph_external_config(
|
|
pecan.request.context, sb_obj.get('ceph_conf'))
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
msg = _("Failed to distribute ceph config file.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
services = api_helper.getListFromServices(sb_obj)
|
|
|
|
pecan.request.rpcapi.update_ceph_external_config(
|
|
pecan.request.context,
|
|
sb_obj.get('uuid'),
|
|
services)
|
|
elif op == constants.SB_API_OP_DELETE:
|
|
msg = _("Delete a Ceph external backend is not supported currently.")
|
|
raise wsme.exc.ClientSideError(msg)
|
|
else:
|
|
# Compare ceph pools
|
|
caps = sb_obj.get('capabilities', {})
|
|
orig_caps = orig_sb_obj.get('capabilities', {})
|
|
services = []
|
|
for svc in constants.SB_CEPH_EXTERNAL_SVCS_SUPPORTED:
|
|
for k in HIERA_DATA[svc]:
|
|
if caps.get(k, None) != orig_caps.get(k, None):
|
|
services.append(svc)
|
|
|
|
pecan.request.rpcapi.update_ceph_external_config(
|
|
pecan.request.context,
|
|
sb_obj.get('uuid'),
|
|
services)
|
|
|
|
|
|
def _set_defaults_ceph_external(storage_ceph_ext):
|
|
defaults = {
|
|
'backend': constants.SB_TYPE_CEPH_EXTERNAL,
|
|
'name': constants.SB_DEFAULT_NAMES[constants.SB_TYPE_CEPH_EXTERNAL].format(0),
|
|
'state': constants.SB_STATE_CONFIGURING,
|
|
'task': None,
|
|
'services': None,
|
|
'ceph_conf': None,
|
|
'capabilities': {},
|
|
}
|
|
sc = api_helper.set_backend_data(storage_ceph_ext,
|
|
defaults,
|
|
HIERA_DATA,
|
|
constants.SB_CEPH_EXTERNAL_SVCS_SUPPORTED)
|
|
|
|
return sc
|
|
|
|
|
|
def _create(storage_ceph_ext):
|
|
storage_ceph_ext = _set_defaults_ceph_external(storage_ceph_ext)
|
|
|
|
# Execute the common semantic checks for all backends, if a specific backend
|
|
# is not specified this will not return
|
|
api_helper.common_checks(constants.SB_API_OP_CREATE,
|
|
storage_ceph_ext)
|
|
|
|
_check_backend_ceph_external(storage_ceph_ext)
|
|
|
|
_check_and_update_services(storage_ceph_ext)
|
|
|
|
# Conditionally update the DB based on any previous create attempts. This
|
|
# creates the StorageCeph object.
|
|
system = pecan.request.dbapi.isystem_get_one()
|
|
storage_ceph_ext['forisystemid'] = system.id
|
|
storage_ceph_ext_obj = pecan.request.dbapi.storage_ceph_external_create(
|
|
storage_ceph_ext)
|
|
|
|
# Retrieve the main StorageBackend object.
|
|
storage_backend_obj = pecan.request.dbapi.storage_backend_get(
|
|
storage_ceph_ext_obj.id)
|
|
|
|
_apply_ceph_external_backend_changes(
|
|
constants.SB_API_OP_CREATE, sb_obj=storage_ceph_ext)
|
|
|
|
return storage_backend_obj
|
|
|
|
|
|
#
|
|
# Update/Modify/Patch
|
|
#
|
|
|
|
|
|
def _hiera_data_semantic_checks(caps_dict):
|
|
""" Validate each individual data value to make sure it's of the correct
|
|
type and value.
|
|
"""
|
|
pass
|
|
|
|
|
|
def _pre_patch_checks(storage_ceph_ext_obj, patch_obj):
|
|
storage_ceph_ext_dict = storage_ceph_ext_obj.as_dict()
|
|
for p in patch_obj:
|
|
if p['path'] == '/capabilities':
|
|
patch_caps_dict = p['value']
|
|
|
|
# Validate the change to make sure it valid
|
|
_hiera_data_semantic_checks(patch_caps_dict)
|
|
|
|
current_caps_dict = storage_ceph_ext_dict.get('capabilities', {})
|
|
for k in (set(current_caps_dict.keys()) -
|
|
set(patch_caps_dict.keys())):
|
|
patch_caps_dict[k] = current_caps_dict[k]
|
|
|
|
p['value'] = patch_caps_dict
|
|
elif p['path'] == '/services':
|
|
current_svcs = set([])
|
|
if storage_ceph_ext_obj.services:
|
|
current_svcs = set(storage_ceph_ext_obj.services.split(','))
|
|
updated_svcs = set(p['value'].split(','))
|
|
|
|
# WEI: Only support service add. Removing a service is not supported.
|
|
if len(current_svcs - updated_svcs):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Removing %s is not supported.") % ','.join(
|
|
current_svcs - updated_svcs))
|
|
p['value'] = ','.join(updated_svcs)
|
|
elif p['path'] == '/ceph_conf':
|
|
pass
|
|
|
|
|
|
def _patch(stor_ceph_ext_uuid, patch):
|
|
|
|
# Obtain current storage object.
|
|
rpc_stor_ceph_ext = objects.storage_ceph_external.get_by_uuid(
|
|
pecan.request.context,
|
|
stor_ceph_ext_uuid)
|
|
|
|
ostor_ceph_ext = copy.deepcopy(rpc_stor_ceph_ext)
|
|
|
|
patch_obj = jsonpatch.JsonPatch(patch)
|
|
for p in patch_obj:
|
|
if p['path'] == '/capabilities':
|
|
p['value'] = jsonutils.loads(p['value'])
|
|
|
|
# perform checks based on the current vs.requested modifications
|
|
_pre_patch_checks(rpc_stor_ceph_ext, patch_obj)
|
|
|
|
# Obtain a storage object with the patch applied.
|
|
try:
|
|
stor_ceph_ext_config = StorageCephExternal(**jsonpatch.apply_patch(
|
|
rpc_stor_ceph_ext.as_dict(),
|
|
patch_obj))
|
|
|
|
except utils.JSONPATCH_EXCEPTIONS as e:
|
|
raise exception.PatchError(patch=patch, reason=e)
|
|
|
|
# Update current storage object.
|
|
for field in objects.storage_ceph_external.fields:
|
|
if (field in stor_ceph_ext_config.as_dict() and
|
|
rpc_stor_ceph_ext[field] != stor_ceph_ext_config.as_dict()[field]):
|
|
rpc_stor_ceph_ext[field] = stor_ceph_ext_config.as_dict()[field]
|
|
|
|
# Obtain the fields that have changed.
|
|
delta = rpc_stor_ceph_ext.obj_what_changed()
|
|
if len(delta) == 0 and rpc_stor_ceph_ext['state'] != constants.SB_STATE_CONFIG_ERR:
|
|
raise wsme.exc.ClientSideError(
|
|
_("No changes to the existing backend settings were detected."))
|
|
|
|
allowed_attributes = ['services', 'ceph_conf', 'capabilities', 'task']
|
|
for d in delta:
|
|
if d not in allowed_attributes:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can not modify '%s' with this operation." % d))
|
|
|
|
LOG.info("SYS_I orig storage_ceph_external: %s " % ostor_ceph_ext.as_dict())
|
|
LOG.info("SYS_I new storage_ceph_external: %s " % stor_ceph_ext_config.as_dict())
|
|
|
|
# Execute the common semantic checks for all backends, if backend is not
|
|
# present this will not return
|
|
api_helper.common_checks(constants.SB_API_OP_MODIFY,
|
|
rpc_stor_ceph_ext)
|
|
|
|
_check_backend_ceph_external(rpc_stor_ceph_ext)
|
|
|
|
_check_and_update_services(rpc_stor_ceph_ext)
|
|
|
|
rpc_stor_ceph_ext.save()
|
|
|
|
_apply_ceph_external_backend_changes(
|
|
constants.SB_API_OP_MODIFY, sb_obj=rpc_stor_ceph_ext, orig_sb_obj=ostor_ceph_ext)
|
|
|
|
return StorageCephExternal.convert_with_links(rpc_stor_ceph_ext)
|