Add storage-backend for new ceph-rook

This change the storage-backend-add command, including the following
changes:
 - Check if there is ceph or ceph-rook in the system, only one is
   allowed, never both
 - deployment_model argument, use: -d or --deployment
 - Initial state for ceph-rook backend is configuring_on_unlock when
   host is locked
 - Initial state for ceph-rook backend is configuring when host is
   unlocked
 - Update Initial state from configuring_on_unlock to configuring when
   host is unlocked
 - Add default services for ceph-rook backend
 - Add supported services check for ceph-rook backend

Test Plan:
 - PASS: allow/not allow add ceph-rook backend if ceph already in system
 - PASS: Add deployment_model in capabilities column in storage_backend
 - PASS: Check storage_backend state is aligned with host lock/unlock
 - PASS: Check if when host is unlocked the storage_backend state was
         updated.
 - PASS: Check storage_backend services was added the default values
 - PASS: Check if it is not allowed to insert another type of service in
         storage_backend services

Story: 2011117
Task: 50062

Change-Id: I5be1323d3ac08cff02d859bd8e414814bc378c4f
Signed-off-by: Gustavo Ornaghi Antunes <gustavo.ornaghiantunes@windriver.com>
This commit is contained in:
Gustavo Ornaghi Antunes 2024-05-09 12:09:06 -03:00
parent 5ca9c6f78f
commit 6cac649034
8 changed files with 212 additions and 50 deletions

View File

@ -13,6 +13,7 @@ from cgtsclient.common import utils
from cgtsclient import exc
from cgtsclient.v1 import storage_ceph # noqa
from cgtsclient.v1 import storage_ceph_external # noqa
from cgtsclient.v1 import storage_ceph_rook # noqa
from cgtsclient.v1 import storage_external # noqa
from cgtsclient.v1 import storage_file # noqa
from cgtsclient.v1 import storage_lvm # noqa
@ -131,14 +132,14 @@ def _display_next_steps():
def backend_add(cc, backend, args):
backend = backend.replace('-', '_')
backend_filename = backend.replace('-', '_')
# allowed storage_backend fields
allowed_fields = ['name', 'services', 'confirmed', 'ceph_conf']
# allowed backend specific backends
if backend in constants.SB_SUPPORTED:
backend_attrs = getattr(eval('storage_' + backend),
backend_attrs = getattr(eval('storage_' + backend_filename),
'CREATION_ATTRIBUTES')
allowed_fields = list(set(allowed_fields + backend_attrs))
@ -158,7 +159,7 @@ def backend_add(cc, backend, args):
if not fields['capabilities']:
del fields['capabilities']
backend_client = getattr(cc, 'storage_' + backend)
backend_client = getattr(cc, 'storage_' + backend_filename)
backend_client.create(**fields)
_display_next_steps()

View File

@ -71,6 +71,10 @@ def do_storage_backend_show(cc, args):
metavar='<tier_uuid>',
help=('Optional storage tier uuid for additional backends (ceph '
'only)'))
@utils.arg('-d', '--deployment',
metavar='<deployment_model>',
help=('Optional deployment_model, default: controller ( Ceph-rook '
'only )'))
@utils.arg('-c', '--ceph_conf',
metavar='<ceph_conf>',
help='Location of the Ceph configuration file used for provisioning'

View File

@ -10,7 +10,8 @@
from cgtsclient.common import base
from cgtsclient import exc
CREATION_ATTRIBUTES = ['confirmed', 'name', 'services', 'capabilities']
CREATION_ATTRIBUTES = ['confirmed', 'name', 'services', 'capabilities',
'deployment']
DISPLAY_ATTRIBUTES = []
PATCH_ATTRIBUTES = []

View File

@ -2402,6 +2402,7 @@ class HostController(rest.RestController):
if hostupdate.ihost_patch['operational'] == \
constants.OPERATIONAL_ENABLED:
self._update_add_ceph_state()
self._update_add_ceph_rook_state()
if hostupdate.notify_availability:
if (hostupdate.notify_availability ==
@ -4596,9 +4597,13 @@ class HostController(rest.RestController):
# deny operation if any storage backend is either configuring or in error
backends = pecan.request.dbapi.storage_backend_get_list()
for bk in backends:
if bk['state'] != constants.SB_STATE_CONFIGURED:
if (bk['state'] != constants.SB_STATE_CONFIGURED and
(bk['state'] != constants.SB_STATE_CONFIGURING_ON_UNLOCK and
bk['backend'] != constants.SB_TYPE_CEPH_ROOK
)):
# TODO(oponcea): Remove once sm supports in-service configuration
if (bk['backend'] != constants.SB_TYPE_CEPH or
if ((bk['backend'] != constants.SB_TYPE_CEPH and
bk['backend'] != constants.SB_TYPE_CEPH_ROOK) or
bk['task'] != constants.SB_TASK_PROVISION_STORAGE or
ihost['personality'] != constants.CONTROLLER):
msg = _("%(backend)s is %(notok)s. All storage backends must "
@ -5170,6 +5175,23 @@ class HostController(rest.RestController):
else:
return False
def _update_add_ceph_rook_state(self):
api = pecan.request.dbapi
backend = StorageBackendConfig.get_configuring_on_unlock_backend(api)
if backend and backend.backend == constants.SB_TYPE_CEPH_ROOK:
ihosts = api.ihost_get_by_personality(
constants.CONTROLLER
)
for ihost in ihosts:
if ihost.config_status == constants.CONFIG_STATUS_OUT_OF_DATE:
return
api.storage_backend_update(backend.uuid, {
'state': constants.SB_STATE_CONFIGURING
})
def _update_add_ceph_state(self):
api = pecan.request.dbapi

View File

@ -46,10 +46,11 @@ from sysinv import objects
LOG = log.getLogger(__name__)
HIERA_DATA = {
'backend': [],
constants.SB_SVC_GLANCE: [],
constants.SB_SVC_CINDER: [],
constants. SB_SVC_NOVA: [],
'backend': [constants.CEPH_ROOK_BACKEND_DEPLOYMENT_CAP],
constants. SB_SVC_BLOCK: [],
constants. SB_SVC_ECBLOCK: [],
constants. SB_SVC_FILESYSTEM: [],
constants. SB_SVC_OBJECT: [],
}
@ -81,7 +82,7 @@ class StorageCephRook(base.APIBase):
"Represents the storage backend (file, lvm, or ceph)."
state = wtypes.text
"The state of the backend. It can be configured or configuring."
"The state of the backend. It can be configured, configuring or configuring_on_unlock."
name = wtypes.text
"The name of the backend (to differentiate between multiple common backends)."
@ -106,7 +107,7 @@ class StorageCephRook(base.APIBase):
def __init__(self, **kwargs):
defaults = {'uuid': uuidutils.generate_uuid(),
'state': constants.SB_STATE_CONFIGURING,
'state': None,
'task': constants.SB_TASK_NONE,
'capabilities': {},
'services': None,
@ -295,21 +296,48 @@ def _discover_and_validate_backend_hiera_data(caps_dict):
# Currently there is no backend specific hiera_data for this backend
pass
def _discover_and_validate_glance_hiera_data(caps_dict):
def _discover_and_validate_block_hiera_data(caps_dict):
# Currently there is no backend specific hiera_data for this backend
pass
def _discover_and_validate_cinder_hiera_data(caps_dict):
def _discover_and_validate_ecblock_hiera_data(caps_dict):
# Currently there is no backend specific hiera_data for this backend
pass
def _discover_and_validate_nova_hiera_data(caps_dict):
def _discover_and_validate_filesystem_hiera_data(caps_dict):
# Currently there is no backend specific hiera_data for this backend
pass
def _discover_and_validate_object_hiera_data(caps_dict):
# Currently there is no backend specific hiera_data for this backend
pass
def _create_default_ceph_rook_db_entries():
try:
isystem = pecan.request.dbapi.isystem_get_one()
except exception.NotFound:
# When adding the backend, the system DB entry should
# have already been created, but it's safer to just check
LOG.info('System is not configured. Cannot create Cluster '
'DB entry')
return
LOG.info("Create new ceph rook cluster record")
# Create the default primary cluster
db_cluster = pecan.request.dbapi.cluster_create(
{'uuid': uuidutils.generate_uuid(),
'cluster_uuid': None,
'type': constants.SB_TYPE_CEPH_ROOK,
'name': constants.CLUSTER_CEPH_ROOK_DEFAULT_NAME,
'system_id': isystem.id})
# Create the default primary ceph storage tier
LOG.info("Create primary ceph rook tier record.")
pecan.request.dbapi.storage_tier_create(
{'forclusterid': db_cluster.id,
'name': constants.SB_TIER_DEFAULT_NAMES[constants.SB_TIER_TYPE_CEPH],
'type': constants.SB_TIER_TYPE_CEPH,
'status': constants.SB_TIER_STATUS_DEFINED,
'capabilities': {}})
def _check_backend_ceph_rook(req, storage_ceph_rook, confirmed=False):
# check for the backend parameters
@ -325,6 +353,12 @@ def _check_backend_ceph_rook(req, storage_ceph_rook, confirmed=False):
# go through the service list and validate
req_services = api_helper.getListFromServices(storage_ceph_rook)
if (constants.SB_SVC_BLOCK in req_services and
constants.SB_SVC_ECBLOCK in req_services):
raise wsme.exc.ClientSideError("Service %s and %s are not supported for the"
" %s backend in same time" %
(constants.SB_SVC_BLOCK, constants.SB_SVC_ECBLOCK, constants.SB_TYPE_CEPH_ROOK))
for svc in req_services:
if svc not in constants.SB_CEPH_ROOK_SVCS_SUPPORTED:
raise wsme.exc.ClientSideError("Service %s is not supported for the"
@ -341,10 +375,42 @@ def _check_backend_ceph_rook(req, storage_ceph_rook, confirmed=False):
raise wsme.exc.ClientSideError("Missing required %s service "
"parameter: %s" % (svc, k))
# Update based on any discovered values
storage_ceph_rook['capabilities'] = capabilities
storage_ceph_rook['state'] = constants.SB_STATE_CONFIGURED
storage_ceph_rook['task'] = constants.SB_TASK_NONE
# Additional checks based on operation
if req == constants.SB_API_OP_CREATE:
# The ceph-rook backend must be associated with a storage tier
tierId = storage_ceph_rook.get('tier_id') or storage_ceph_rook.get('tier_uuid')
if not tierId:
if api_helper.is_primary_ceph_rook_backend(storage_ceph_rook['name']):
# Adding the default ceph backend, use the default ceph tier
try:
tier = pecan.request.dbapi.storage_tier_query(
{'name': constants.SB_TIER_DEFAULT_NAMES[
constants.SB_TIER_TYPE_CEPH]})
except exception.StorageTierNotFoundByName:
try:
# When we try to create the default storage backend
# it expects the default cluster and storage tier
# to be already created.
# They were initially created when conductor started,
# but since ceph is no longer enabled by default, we
# should just create it here.
_create_default_ceph_rook_db_entries()
tier = pecan.request.dbapi.storage_tier_query(
{'name': constants.SB_TIER_DEFAULT_NAMES[
constants.SB_TIER_TYPE_CEPH]})
except Exception as e:
LOG.exception(e)
raise wsme.exc.ClientSideError(
_("Error creating default ceph database entries"))
else:
raise wsme.exc.ClientSideError(_("No tier specified for this "
"backend."))
else:
try:
tier = pecan.request.dbapi.storage_tier_get(tierId)
except exception.StorageTierNotFound:
raise wsme.exc.ClientSideError(_("No tier with uuid %s found.") % tierId)
storage_ceph_rook.update({'tier_id': tier.id})
# Check for confirmation
if not confirmed:
@ -358,16 +424,7 @@ def _check_backend_ceph_rook(req, storage_ceph_rook, confirmed=False):
def _apply_backend_changes(op, sb_obj):
if op == constants.SB_API_OP_CREATE:
# This is a DB only change => Set the state to configured
pecan.request.dbapi.storage_ceph_rook_update(
sb_obj.uuid,
{'state': constants.SB_STATE_CONFIGURED})
services = api_helper.getListFromServices(sb_obj.as_dict())
pecan.request.rpcapi.update_ceph_rook_config(
pecan.request.context,
sb_obj.get('uuid'),
services)
pass
elif op == constants.SB_API_OP_MODIFY:
pass
@ -381,13 +438,34 @@ def _apply_backend_changes(op, sb_obj):
#
def _set_default_values(storage_ceph_rook):
deployment = storage_ceph_rook.get('deployment', '')
if deployment not in constants.CEPH_ROOK_DEPLOYMENT_SUPPORTED:
deployment = constants.CEPH_ROOK_CONTROLLER_MODEL
def_services = f'{constants.SB_SVC_BLOCK},{constants.SB_SVC_FILESYSTEM}'
def_capabilities = {
constants.CEPH_ROOK_BACKEND_DEPLOYMENT_CAP: deployment
}
def_state = constants.SB_STATE_CONFIGURING_ON_UNLOCK
# set configuring state only if all controllers are unlocked
controller_hosts = pecan.request.dbapi.ihost_get_by_personality(
constants.CONTROLLER)
valid_controller_hosts = [h for h in controller_hosts if
h['administrative'] == constants.ADMIN_UNLOCKED and
h['availability'] in [constants.AVAILABILITY_AVAILABLE,
constants.AVAILABILITY_DEGRADED]]
if valid_controller_hosts:
def_state = constants.SB_STATE_CONFIGURING
defaults = {
'backend': constants.SB_TYPE_CEPH_ROOK,
'name': constants.SB_DEFAULT_NAMES[constants.SB_TYPE_CEPH_ROOK],
'state': constants.SB_STATE_CONFIGURING,
'state': def_state,
'task': constants.SB_TASK_NONE,
'services': None,
'capabilities': {}
'services': def_services,
'capabilities': def_capabilities
}
sf = api_helper.set_backend_data(storage_ceph_rook,
@ -416,20 +494,6 @@ def _create(storage_ceph_rook):
storage_ceph_rook['forisystemid'] = system.id
storage_ceph_rook_obj = pecan.request.dbapi.storage_ceph_rook_create(storage_ceph_rook)
# Retreive the main StorageBackend object.
storage_backend_obj = pecan.request.dbapi.storage_backend_get(storage_ceph_rook_obj.id)
# Only apply runtime manifests if at least one controller is unlocked and
# available/degraded.
controller_hosts = pecan.request.dbapi.ihost_get_by_personality(
constants.CONTROLLER)
valid_controller_hosts = [h for h in controller_hosts if
h['administrative'] == constants.ADMIN_UNLOCKED and
h['availability'] in [constants.AVAILABILITY_AVAILABLE,
constants.AVAILABILITY_DEGRADED]]
if valid_controller_hosts:
_apply_backend_changes(constants.SB_API_OP_CREATE, storage_backend_obj)
return storage_ceph_rook_obj

View File

@ -685,6 +685,13 @@ class SBApiHelper(object):
if not existing_backend:
existing_backends_by_type = set(bk['backend'] for bk in backends)
ceph_backend_list = [constants.SB_TYPE_CEPH, constants.SB_TYPE_CEPH_ROOK]
for backend in ceph_backend_list:
if (backend in existing_backends_by_type and
backend_type not in existing_backends_by_type):
msg = _("Only one backend between %s is supported." % ", ".join(ceph_backend_list))
raise wsme.exc.ClientSideError(msg)
if (backend_type in existing_backends_by_type and
backend_type not in [constants.SB_TYPE_CEPH, constants.SB_TYPE_CEPH_EXTERNAL]):
@ -857,6 +864,13 @@ class SBApiHelper(object):
return True
return False
@staticmethod
def is_primary_ceph_rook_backend(name_string):
"""Check if a backend name string is for the primary ceph backend. """
if name_string == constants.SB_DEFAULT_NAMES[constants.SB_TYPE_CEPH_ROOK]:
return True
return False
@staticmethod
def remove_service_from_backend(sb, svc_name):
services = SBApiHelper.getListFromServices(sb)

View File

@ -480,6 +480,11 @@ SB_SVC_NOVA = 'nova'
SB_SVC_SWIFT = 'swift'
SB_SVC_RBD_PROVISIONER = 'rbd-provisioner'
SB_SVC_BLOCK = 'block'
SB_SVC_ECBLOCK = 'ecblock'
SB_SVC_FILESYSTEM = 'filesystem'
SB_SVC_OBJECT = 'object'
SB_FILE_SVCS_SUPPORTED = [SB_SVC_GLANCE]
SB_LVM_SVCS_SUPPORTED = [SB_SVC_CINDER]
# Primary tier supported services.
@ -487,7 +492,7 @@ SB_CEPH_SVCS_SUPPORTED = [SB_SVC_GLANCE, SB_SVC_CINDER, SB_SVC_SWIFT,
SB_SVC_NOVA, SB_SVC_RBD_PROVISIONER]
SB_CEPH_EXTERNAL_SVCS_SUPPORTED = [SB_SVC_CINDER, SB_SVC_GLANCE, SB_SVC_NOVA]
SB_EXTERNAL_SVCS_SUPPORTED = [SB_SVC_CINDER, SB_SVC_GLANCE]
SB_CEPH_ROOK_SVCS_SUPPORTED = [SB_SVC_GLANCE, SB_SVC_CINDER, SB_SVC_NOVA]
SB_CEPH_ROOK_SVCS_SUPPORTED = [SB_SVC_BLOCK, SB_SVC_ECBLOCK, SB_SVC_FILESYSTEM, SB_SVC_OBJECT]
# Storage backend: Service specific backend nomenclature
CINDER_BACKEND_CEPH = SB_TYPE_CEPH
@ -501,6 +506,7 @@ GLANCE_BACKEND_GLANCE = 'glance'
# Clusters
CLUSTER_TYPE_CEPH = "ceph"
CLUSTER_CEPH_DEFAULT_NAME = "ceph_cluster"
CLUSTER_CEPH_ROOK_DEFAULT_NAME = "ceph_rook_cluster"
# Storage Tiers: types (aligns with polymorphic backends)
SB_TIER_TYPE_CEPH = SB_TYPE_CEPH
@ -563,6 +569,18 @@ CEPH_CONTROLLER_MODEL = 'controller-nodes'
CEPH_AIO_SX_MODEL = 'aio-sx'
CEPH_UNDEFINED_MODEL = 'undefined'
# Ceph-rook capabilities fields
CEPH_ROOK_BACKEND_DEPLOYMENT_CAP = 'deployment_model'
# Ceph-rook deployment models
CEPH_ROOK_CONTROLLER_MODEL = 'controller'
CEPH_ROOK_DEDICATED_MODEL = 'dedicated'
CEPH_ROOK_OPEN_MODEL = 'open'
CEPH_ROOK_DEPLOYMENT_SUPPORTED = [CEPH_ROOK_CONTROLLER_MODEL,
CEPH_ROOK_DEDICATED_MODEL,
CEPH_ROOK_OPEN_MODEL]
# Storage: Minimum number of monitors
MIN_STOR_MONITORS_MULTINODE = 2
MIN_STOR_MONITORS_AIO = 1

View File

@ -127,6 +127,21 @@ class StorageBackendConfig(object):
# it is normal there isn't one being configured
return None
@staticmethod
def get_configuring_on_unlock_backend(api):
"""Get the primary backend that is configuring. """
backend_list = api.storage_backend_get_list()
for backend in backend_list:
if backend.state == constants.SB_STATE_CONFIGURING_ON_UNLOCK and \
backend.name == constants.SB_DEFAULT_NAMES[backend.backend]:
# At this point we can have but only max 1 configuring backend
# at any moment
return backend
# it is normal there isn't one being configured
return None
@staticmethod
def get_configuring_target_backend(api, target):
"""Get the primary backend that is configuring. """
@ -177,6 +192,29 @@ class StorageBackendConfig(object):
return False
@staticmethod
def has_backend_configuring(dbapi, target, service=None,
check_only_defaults=True):
""" Check if a backend is configuring. """
backend_list = dbapi.storage_backend_get_list()
for backend in backend_list:
LOG.info(backend.state)
if backend.state == constants.SB_STATE_CONFIGURING and \
backend.backend == target:
# Check if the backend name matches the default name
if check_only_defaults and \
backend.name != constants.SB_DEFAULT_NAMES[target]:
continue
# Check if a specific service is configured on the
# backend.
if service and service not in backend.services:
continue
return True
return False
@staticmethod
def has_backend(api, target):
backend_list = api.storage_backend_get_list()