Allow configurable ceph storage backend network

Ceph components are configured by puppet. Hieradata for it is generated
by sysinv. Currently the configuration is generated using IPs from the
management network.

Allow ceph components to be configurable on cluster-host network.
Create a 'network' parameter for strorage-backend-add command. The
parameter is optional, the rest controller will default the value to
management network.

Example usage:
system storage-backend-add --network cluster-host ceph --confirmed
system storage-backend-add --network mgmt ceph --confirmed
system storage-backend-add ceph --confirmed

Updated unit tests.
Added unit test for component that generates the ceph monitor ips for
hieradata.

Tests:
1) AIO-SX: add ceph using --network cluster-host, unlock, ip is from
cluster-host network.
2) STANDARD 2+2: full deploy, no --network parameter, everything as
before.
3) storage-backend-modify and storage-backend-add allows only 'mgmt' and
'cluster-host' network parameter.
4) storage-backend-modify does modify the network before the first
unlock; is rejected for network parameter after first unlock.

Story: 2008843
Task: 42350
Task: 42351
Signed-off-by: Dan Voiculeasa <dan.voiculeasa@windriver.com>
Change-Id: I681ccb3302b8f233424bc291e08675a4dc2b10f7
This commit is contained in:
Dan Voiculeasa 2021-04-20 12:16:17 +03:00
parent a40a3bd892
commit ddc6b69dfc
18 changed files with 229 additions and 39 deletions

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2017 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -110,3 +110,11 @@ PARTITION_STATUS_MSG = {
# Partition table types.
PARTITION_TABLE_GPT = "gpt"
PARTITION_TABLE_MSDOS = "msdos"
# Network definitions
NETWORK_TYPE_MGMT = 'mgmt'
NETWORK_TYPE_CLUSTER_HOST = 'cluster-host'
SB_SUPPORTED_NETWORKS = {
SB_TYPE_CEPH: [NETWORK_TYPE_MGMT, NETWORK_TYPE_CLUSTER_HOST]
}

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -10,6 +10,7 @@
import argparse
from cgtsclient.common import constants
from cgtsclient.common import utils
from cgtsclient.v1 import storage_backend as storage_backend_utils
@ -88,6 +89,10 @@ def do_storage_backend_show(cc, args):
@utils.arg('--ceph-mon-gib',
metavar='<ceph-mon-gib>',
help='The ceph-mon-lv size in GiB')
@utils.arg('--network',
metavar='<network>',
choices=constants.SB_SUPPORTED_NETWORKS[constants.SB_TYPE_CEPH],
help='Desired network to be used by the backend.')
def do_storage_backend_add(cc, args):
"""Add a storage backend."""

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -13,14 +13,14 @@ from cgtsclient import exc
CREATION_ATTRIBUTES = ['confirmed', 'name', 'services', 'capabilities',
'tier_uuid', 'cinder_pool_gib', 'glance_pool_gib',
'ephemeral_pool_gib', 'object_pool_gib',
'kube_pool_gib', 'object_gateway']
'kube_pool_gib', 'object_gateway', 'network']
DISPLAY_ATTRIBUTES = ['object_gateway', 'ceph_total_space_gib',
'object_pool_gib', 'cinder_pool_gib',
'kube_pool_gib', 'glance_pool_gib', 'ephemeral_pool_gib',
'tier_name', 'tier_uuid']
'tier_name', 'tier_uuid', 'network']
PATCH_ATTRIBUTES = ['object_gateway', 'object_pool_gib',
'cinder_pool_gib', 'glance_pool_gib',
'ephemeral_pool_gib', 'kube_pool_gib']
'ephemeral_pool_gib', 'kube_pool_gib', 'network']
class StorageCeph(base.Resource):

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
import pecan
@ -101,6 +101,10 @@ class StorageBackend(base.APIBase):
confirmed = types.boolean
"Represent confirmation that the backend operation should proceed"
# Network parameter: [API-only field for some backends]
network = wtypes.text
"The network for backend components"
def __init__(self, **kwargs):
defaults = {'uuid': uuidutils.generate_uuid(),
'state': constants.SB_STATE_CONFIGURING,
@ -115,6 +119,11 @@ class StorageBackend(base.APIBase):
# (it's an API-only attribute)
self.fields.append('confirmed')
# 'network' is not part of objects.storage_backend.fields
# (it's an API-only attribute passed to specific backend
# implementation such as storage_ceph)
self.fields.append('network')
for k in self.fields:
setattr(self, k, kwargs.get(k, defaults.get(k)))

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
import copy
@ -169,6 +169,9 @@ class StorageCeph(base.APIBase):
confirmed = types.boolean
"Represent confirmation that the backend operation should proceed"
network = wtypes.text
"The network for backend components"
def __init__(self, **kwargs):
defaults = {'uuid': uuidutils.generate_uuid(),
'state': constants.SB_STATE_CONFIGURING,
@ -223,7 +226,8 @@ class StorageCeph(base.APIBase):
'object_gateway',
'ceph_total_space_gib',
'tier_name',
'tier_uuid'])
'tier_uuid',
'network'])
stor_ceph.links =\
[link.Link.make_link('self', pecan.request.host_url,
@ -427,7 +431,7 @@ def _discover_and_validate_backend_config_data(caps_dict, confirmed):
# TODO(oponcea): remove condition once ceph_mon code is refactored.
if confirmed:
try:
StorageBackendConfig.get_ceph_mon_ip_addresses(pecan.request.dbapi)
pecan.request.dbapi.ceph_mon_get_list()
except exception.IncompleteCephMonNetworkConfig as e:
LOG.exception(e)
raise wsme.exc.ClientSideError(_('Ceph Monitor configuration is '
@ -620,6 +624,20 @@ def _check_backend_ceph(req, storage_ceph, confirmed=False):
# "the %s backend" %
# (req, constants.SB_TYPE_CEPH))
# Check network type
if storage_ceph['network'] not in \
constants.SB_SUPPORTED_NETWORKS[constants.SB_TYPE_CEPH]:
raise wsme.exc.ClientSideError(
_("Backend %s not allowed on network %s"
% (constants.SB_TYPE_CEPH, storage_ceph['network'])))
# Check network exists
networks = pecan.request.dbapi.networks_get_by_type(storage_ceph['network'])
if not len(networks):
raise wsme.exc.ClientSideError(
_("Make sure network %s exists"
% (storage_ceph['network'])))
# Check for confirmation
if not confirmed and api_helper.is_primary_ceph_tier(tier.name):
_options_str = _get_options_string(storage_ceph)
@ -799,6 +817,7 @@ def _set_defaults(storage_ceph):
'object_pool_gib': None,
'kube_pool_gib': None,
'object_gateway': False,
'network': constants.NETWORK_TYPE_MGMT,
}
sc = api_helper.set_backend_data(storage_ceph,
@ -1264,7 +1283,9 @@ def _patch(storceph_uuid, patch):
'ephemeral_pool_gib',
'object_pool_gib',
'kube_pool_gib',
'object_gateway']
'object_gateway',
'network']
# TODO(CephPoolsDecouple): remove variable
quota_attributes = ['cinder_pool_gib', 'glance_pool_gib',
'ephemeral_pool_gib', 'object_pool_gib',
@ -1314,6 +1335,7 @@ def _patch(storceph_uuid, patch):
# TODO(CephPoolsDecouple): remove variable
quota_only_update = True
replication_only_update = False
network_update = False
for d in delta:
if d not in allowed_attributes:
raise wsme.exc.ClientSideError(
@ -1378,6 +1400,13 @@ def _patch(storceph_uuid, patch):
constants.CEPH_BACKEND_MIN_REPLICATION_CAP in new_cap):
replication_only_update = True
elif d == 'network':
if cutils.is_initial_config_complete():
raise wsme.exc.ClientSideError(
_("Cannot modify the network after the initial "
"configuration completed."))
network_update = True
LOG.info("SYS_I orig storage_ceph: %s " % ostorceph.as_dict())
LOG.info("SYS_I patched storage_ceph: %s " % storceph_config.as_dict())
@ -1413,7 +1442,8 @@ def _patch(storceph_uuid, patch):
# TODO(CephPoolsDecouple): rework - remove quota_only_update
if ((not quota_only_update and
not fast_config and
not replication_only_update) or
not replication_only_update and
not network_update) or
(storceph_config.state == constants.SB_STATE_CONFIG_ERR)):
# Enable the backend changes:
_apply_backend_changes(constants.SB_API_OP_MODIFY,

View File

@ -17,7 +17,7 @@
# under the License.
#
#
# Copyright (c) 2018 Wind River Systems, Inc.
# Copyright (c) 2018-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -108,6 +108,10 @@ class StorageCephExternal(base.APIBase):
confirmed = types.boolean
"Represent confirmation that the backend operation should proceed"
# Network parameter: [API-only field]
network = wtypes.text
"The network for backend components"
def __init__(self, **kwargs):
defaults = {'uuid': uuidutils.generate_uuid(),
'state': constants.SB_STATE_CONFIGURING,

View File

@ -14,7 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
# Copyright (c) 2020 Intel Corporation, Inc
#
@ -100,6 +100,10 @@ class StorageCephRook(base.APIBase):
confirmed = types.boolean
"Represent confirmation that the backend operation should proceed"
# Network parameter: [API-only field]
network = wtypes.text
"The network for backend components"
def __init__(self, **kwargs):
defaults = {'uuid': uuidutils.generate_uuid(),
'state': constants.SB_STATE_CONFIGURING,

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
import ast
@ -101,6 +101,10 @@ class StorageExternal(base.APIBase):
confirmed = types.boolean
"Represent confirmation that the backend operation should proceed"
# Network parameter: [API-only field]
network = wtypes.text
"The network for backend components"
def __init__(self, **kwargs):
defaults = {'uuid': uuidutils.generate_uuid(),
'state': constants.SB_STATE_CONFIGURING,

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
import copy
@ -99,6 +99,10 @@ class StorageFile(base.APIBase):
confirmed = types.boolean
"Represent confirmation that the backend operation should proceed"
# Network parameter: [API-only field]
network = wtypes.text
"The network for backend components"
def __init__(self, **kwargs):
defaults = {'uuid': uuidutils.generate_uuid(),
'state': constants.SB_STATE_CONFIGURING,

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
import copy
@ -100,6 +100,10 @@ class StorageLVM(base.APIBase):
confirmed = types.boolean
"Represent confirmation that the backend operation should proceed"
# Network parameter: [API-only field]
network = wtypes.text
"The network for backend components"
def __init__(self, **kwargs):
defaults = {'uuid': uuidutils.generate_uuid(),
'state': constants.SB_STATE_CONFIGURING,

View File

@ -1825,3 +1825,8 @@ CERT_MODE_TO_SECRET_NAME = {
CERT_MODE_SSL: RESTAPI_CERT_SECRET_NAME,
CERT_MODE_DOCKER_REGISTRY: REGISTRY_CERT_SECRET_NAME
}
# Storage associated networks
SB_SUPPORTED_NETWORKS = {
SB_TYPE_CEPH: [NETWORK_TYPE_MGMT, NETWORK_TYPE_CLUSTER_HOST]
}

View File

@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2016-2018 Wind River Systems, Inc.
# Copyright (c) 2016-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -211,10 +211,14 @@ class StorageBackendConfig(object):
host = mon['hostname']
if host not in host2ph:
host2ph[host] = constants.CEPH_MON_2
network_type = dbapi.storage_ceph_get_list()[
0].get('network', constants.NETWORK_TYPE_MGMT)
# map host interface to ceph-mon ip placeholder
hostif2ph = {}
for host, ph in host2ph.items():
hostif = '%s-%s' % (host, constants.NETWORK_TYPE_MGMT)
hostif = '%s-%s' % (host, network_type)
hostif2ph[hostif] = ph
# map placeholder to ceph-mon ip address
ph2ipaddr = {}

View File

@ -0,0 +1,38 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sqlalchemy import Column
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sysinv.common import constants
ENGINE = 'InnoDB'
CHARSET = 'utf8'
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
# add network to storage_ceph table
storage_ceph = Table(
'storage_ceph',
meta,
mysql_engine=ENGINE,
mysql_charset=CHARSET,
autoload=True)
col = Column('network', String(255), nullable=True, default=constants.NETWORK_TYPE_MGMT)
col.create(storage_ceph)
col.alter(nullable=False)
def downgrade(migrate_engine):
# As per other openstack components, downgrade is
# unsupported in this release.
raise NotImplementedError('SysInv database downgrade is unsupported.')

View File

@ -845,6 +845,7 @@ class StorageCeph(StorageBackend):
object_pool_gib = Column(Integer)
kube_pool_gib = Column(Integer)
object_gateway = Column(Boolean, default=False)
network = Column(String(255), default=constants.NETWORK_TYPE_MGMT)
tier_id = Column(Integer,
ForeignKey('storage_tiers.id'))

View File

@ -275,19 +275,36 @@ class BaseHelm(object):
def _system_mode(self):
return self.dbapi.isystem_get_one().system_mode
def _get_filtered_ceph_monitor_ips_using_function(self, name_filter):
""" Extracts the ceph monitor ips to a list based on a filter
:param name_filter: A filter function returning a boolean.
:returns: List of filtered monitor ips.
"""
monitors = []
for name, addr in StorageBackendConfig.get_ceph_mon_ip_addresses(
self.dbapi).items():
if name_filter(name):
monitors.append(addr)
return monitors
def _get_filtered_ceph_monitor_ips_by_name(self, name):
return self._get_filtered_ceph_monitor_ips_using_function(
lambda x: True if x == name else False)
def _get_ceph_monitor_ips(self, name_filter=None):
system = self._get_system()
if system.system_type == constants.TIS_AIO_BUILD:
if system.system_mode == constants.SYSTEM_MODE_SIMPLEX:
monitors = [self._get_controller_0_management_address()]
# ceph monitor for controller-0
monitors = self._get_filtered_ceph_monitor_ips_by_name(constants.CEPH_MON_0)
else:
monitors = [self._get_management_address()]
# ceph monitor for controller-floating
monitors = self._get_filtered_ceph_monitor_ips_by_name(constants.CEPH_FLOATING_MON)
elif name_filter:
monitors = []
for name, addr in StorageBackendConfig.get_ceph_mon_ip_addresses(
self.dbapi).items():
if name_filter(name):
monitors.append(addr)
monitors = self._get_filtered_ceph_monitor_ips_using_function(name_filter)
else:
monitors = StorageBackendConfig.get_ceph_mon_ip_addresses(
self.dbapi).values()
@ -301,16 +318,6 @@ class BaseHelm(object):
]
return formatted_monitor_ips
def _get_management_address(self):
address = self._get_address_by_name(
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_MGMT)
return address.address
def _get_controller_0_management_address(self):
address = self._get_address_by_name(
constants.CONTROLLER_0_HOSTNAME, constants.NETWORK_TYPE_MGMT)
return address.address
@staticmethod
def _format_url_address(address):
return utils.format_url_address(address)

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -28,6 +28,7 @@ class StorageCeph(storage_backend.StorageBackend):
'tier_id': utils.int_or_none,
'tier_name': utils.str_or_none,
'tier_uuid': utils.str_or_none,
'network': utils.str_or_none,
}, **storage_backend.StorageBackend.fields)
_foreign_fields = dict({

View File

@ -2,7 +2,7 @@
# -*- encoding: utf-8 -*-
#
#
# Copyright (c) 2017-2018 Wind River Systems, Inc.
# Copyright (c) 2017-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -12,8 +12,10 @@ Tests for the API /storage_backend/ methods.
"""
import mock
from collections import namedtuple
from six.moves import http_client
from sysinv.db import api as dbapi
from sysinv.tests.api import base
from sysinv.tests.db import utils as dbutils
from sysinv.common import constants
@ -84,6 +86,12 @@ class StorageBackendTestCases(base.FunctionalTest):
self.load = dbutils.create_test_load()
self.host = dbutils.create_test_ihost(forisystemid=self.system.id)
# Patch management network for ceph
self.dbapi = dbapi.get_instance()
p = mock.patch.object(self.dbapi, 'networks_get_by_type')
p.start().return_value = [{'network_type': constants.NETWORK_TYPE_MGMT}]
self.addCleanup(p.stop)
def assertDeleted(self, fullPath):
self.get_json(fullPath, expect_errors=True) # Make sure this line raises an error
@ -1223,6 +1231,12 @@ class StorageCephTestCases(base.FunctionalTest):
self.load = dbutils.create_test_load()
self.host = dbutils.create_test_ihost(forisystemid=self.system.id)
# Patch management network for ceph
self.dbapi = dbapi.get_instance()
p = mock.patch.object(self.dbapi, 'networks_get_by_type')
p.start().return_value = [{'network_type': constants.NETWORK_TYPE_MGMT}]
self.addCleanup(p.stop)
def assertDeleted(self, fullPath):
self.get_json(fullPath, expect_errors=True) # Make sure this line raises an error
@ -1358,3 +1372,45 @@ class StorageCephTestCases(base.FunctionalTest):
response.json['uuid'])['backend']) # Result
self.assertEqual(constants.SB_TYPE_CEPH,
self.get_json('/storage_backend')['storage_backends'][0]['backend'])
class StorageBackendConfigTest(base.FunctionalTest):
def setUp(self):
super(StorageBackendConfigTest, self).setUp()
self.dbapi = dbapi.get_instance()
def test_get_ceph_mon_ip_addresses(self):
self._test_get_ceph_mon_ip_addresses(constants.NETWORK_TYPE_MGMT)
self._test_get_ceph_mon_ip_addresses(constants.NETWORK_TYPE_CLUSTER_HOST)
pass
def _test_get_ceph_mon_ip_addresses(self, network_type):
hostnames = [constants.CONTROLLER_HOSTNAME,
constants.CONTROLLER_0_HOSTNAME,
constants.CONTROLLER_1_HOSTNAME]
ips_mock = ['1', '2', '3']
placeholders = [constants.CEPH_FLOATING_MON,
constants.CEPH_MON_0,
constants.CEPH_MON_1]
result_mock = dict(map(lambda x, y: (x, y), placeholders, ips_mock))
addresses = list(map(lambda x: '{}-{}'.format(x, network_type), hostnames))
addresses_mock = \
list(map(lambda x, y: ({'name': x, 'address': y}), addresses, ips_mock))
addresses_mock_object = \
list(map(lambda x: namedtuple("Addresses", x.keys())(*x.values()), addresses_mock))
p = mock.patch.object(self.dbapi, 'ceph_mon_get_list')
p.start().return_value = list(map(lambda x: {'hostname': x}, hostnames))
self.addCleanup(p.stop)
p = mock.patch.object(self.dbapi, 'storage_ceph_get_list')
p.start().return_value = [{'network': network_type}]
self.addCleanup(p.stop)
p = mock.patch.object(self.dbapi, 'addresses_get_all')
p.start().return_value = addresses_mock_object
self.addCleanup(p.stop)
result = StorageBackendConfig.get_ceph_mon_ip_addresses(self.dbapi)
self.assertDictEqual(result, result_mock)

View File

@ -2,7 +2,7 @@
# -*- encoding: utf-8 -*-
#
#
# Copyright (c) 2017-2019 Wind River Systems, Inc.
# Copyright (c) 2017-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -659,6 +659,12 @@ class StorageTierDependentTCs(base.FunctionalTest):
self.assertEqual(peer.name, 'group-0')
self.assertEqual(peer.hosts, [storage_0.hostname])
# Patch management network for ceph
self.dbapi = dbapi.get_instance()
p = mock.patch.object(self.dbapi, 'networks_get_by_type')
p.start().return_value = [{'network_type': constants.NETWORK_TYPE_MGMT}]
self.addCleanup(p.stop)
# Add the default ceph backend
values = {
'backend': constants.SB_TYPE_CEPH,