From c634aba6fd8f799df3421e01b0ba797e82511bf8 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 5 May 2021 13:56:57 +0100 Subject: [PATCH] Enable support for beast frontend Introduce support for the beast web frontend for the Ceph RADOS Gateway which brings improvements to speed and scalability. Default behaviour is changed in that for Octopus and later (aside from some unsupported architectures) beast is enabled by default; for older releases civetweb is still used. This may be overridden using the 'http-frontend' configuration option which accepts either 'beast' or 'civetweb' as valid values. 'beast' is only supported with Ceph Mimic or later. Closes-Bug: 1865396 Change-Id: Ib73e58e21219eca611cd4293da69bf80040f5803 --- config.yaml | 7 +++ hooks/ceph_radosgw_context.py | 68 ++++++++++++++++++++++++- hooks/hooks.py | 7 ++- templates/ceph.conf | 2 +- unit_tests/test_ceph_radosgw_context.py | 68 ++++++++++++++++++++++++- 5 files changed, 148 insertions(+), 4 deletions(-) diff --git a/config.yaml b/config.yaml index a77c5208..422cc903 100644 --- a/config.yaml +++ b/config.yaml @@ -509,3 +509,10 @@ options: NOTE: X-Versions-Location is the only versioning-related header that radosgw interprets. X-History-Location, supported by native OpenStack Swift, is currently not supported by radosgw. + http-frontend: + type: string + default: + description: | + Frontend HTTP engine to use for the Ceph RADOS Gateway; For Octopus and + later this defaults to 'beast' and for older releases (and on architectures + where beast is not supported) 'civetweb'. diff --git a/hooks/ceph_radosgw_context.py b/hooks/ceph_radosgw_context.py index 9f6063d1..197556f6 100644 --- a/hooks/ceph_radosgw_context.py +++ b/hooks/ceph_radosgw_context.py @@ -23,10 +23,14 @@ from charmhelpers.contrib.hahelpers.cluster import ( determine_api_port, determine_apache_port, ) -from charmhelpers.core.host import cmp_pkgrevno +from charmhelpers.core.host import ( + cmp_pkgrevno, + arch, +) from charmhelpers.core.hookenv import ( DEBUG, WARNING, + ERROR, config, log, related_units, @@ -44,6 +48,12 @@ from charmhelpers.contrib.storage.linux.ceph import CephConfContext import utils +BEAST_FRONTEND = 'beast' +CIVETWEB_FRONTEND = 'civetweb' +SUPPORTED_FRONTENDS = (BEAST_FRONTEND, CIVETWEB_FRONTEND) +UNSUPPORTED_BEAST_ARCHS = ('s390x', 'riscv64') + + class ApacheSSLContext(context.ApacheSSLContext): interfaces = ['https'] service_namespace = 'ceph-radosgw' @@ -142,6 +152,55 @@ def ensure_host_resolvable_v6(hostname): shutil.rmtree(dtmp) +def resolve_http_frontend(): + """Automatically determine the HTTP frontend configuration + + Determines the best HTTP frontend configuration based + on the Ceph release in use and the architecture of the + machine being used. + + :returns http frontend configuration to use. + :rtype: str + """ + octopus_or_later = cmp_pkgrevno('radosgw', '15.2.0') >= 0 + pacific_or_later = cmp_pkgrevno('radosgw', '16.2.0') >= 0 + if octopus_or_later: + # Pacific or later supports beast on all architectures + # but octopus does not support s390x or riscv64 + if not pacific_or_later and arch() in UNSUPPORTED_BEAST_ARCHS: + return CIVETWEB_FRONTEND + else: + return BEAST_FRONTEND + return CIVETWEB_FRONTEND + + +def validate_http_frontend(frontend_config): + """Validate HTTP frontend configuration + + :param frontend_config: user provided config value + :type: str + :raises: ValueError if the provided config is not valid + """ + mimic_or_later = cmp_pkgrevno('radosgw', '13.2.0') >= 0 + pacific_or_later = cmp_pkgrevno('radosgw', '16.2.0') >= 0 + if frontend_config not in SUPPORTED_FRONTENDS: + e = ('Please provide either civetweb or beast for ' + 'http-frontend configuration') + log(e, level=ERROR) + raise ValueError(e) + if frontend_config == BEAST_FRONTEND: + if not mimic_or_later: + e = ('Use of the beast HTTP frontend requires Ceph ' + 'mimic or later.') + log(e, level=ERROR) + raise ValueError(e) + if not pacific_or_later and arch() in UNSUPPORTED_BEAST_ARCHS: + e = ('Use of the beast HTTP frontend on {} requires Ceph ' + 'pacific or later.'.format(arch())) + log(e, level=ERROR) + raise ValueError(e) + + class MonContext(context.CephContext): interfaces = ['mon'] @@ -191,6 +250,12 @@ class MonContext(context.CephContext): if config('prefer-ipv6'): port = "[::]:%s" % (port) + http_frontend = config('http-frontend') + if not http_frontend: + http_frontend = resolve_http_frontend() + else: + validate_http_frontend(http_frontend) + mon_hosts.sort() ctxt = { 'auth_supported': auth, @@ -210,6 +275,7 @@ class MonContext(context.CephContext): 'unit_public_ip': unit_public_ip(), 'fsid': fsid, 'rgw_swift_versioning': config('rgw-swift-versioning-enabled'), + 'frontend': http_frontend, } # NOTE(dosaboy): these sections must correspond to what is supported in diff --git a/hooks/hooks.py b/hooks/hooks.py index 040a71e2..abc30460 100755 --- a/hooks/hooks.py +++ b/hooks/hooks.py @@ -42,6 +42,7 @@ from charmhelpers.core.hookenv import ( is_leader, leader_set, leader_get, + WORKLOAD_STATES, ) from charmhelpers.fetch import ( apt_update, @@ -747,4 +748,8 @@ if __name__ == '__main__': hooks.execute(sys.argv) except UnregisteredHookError as e: log('Unknown hook {} - skipping.'.format(e)) - assess_status(CONFIGS) + except ValueError as e: + # Handle any invalid configuration values + status_set(WORKLOAD_STATES.BLOCKED, str(e)) + else: + assess_status(CONFIGS) diff --git a/templates/ceph.conf b/templates/ceph.conf index c255d362..29b5e26b 100644 --- a/templates/ceph.conf +++ b/templates/ceph.conf @@ -39,7 +39,7 @@ rgw_zone = {{ rgw_zone }} {% endif %} rgw init timeout = 1200 -rgw frontends = civetweb port={{ port }} +rgw frontends = {{ frontend }} port={{ port }} {% if auth_type == 'keystone' %} rgw keystone url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/ rgw keystone admin user = {{ admin_user }} diff --git a/unit_tests/test_ceph_radosgw_context.py b/unit_tests/test_ceph_radosgw_context.py index 480f208c..6985571e 100644 --- a/unit_tests/test_ceph_radosgw_context.py +++ b/unit_tests/test_ceph_radosgw_context.py @@ -17,6 +17,7 @@ from mock import patch import ceph_radosgw_context as context import charmhelpers import charmhelpers.contrib.storage.linux.ceph as ceph +import charmhelpers.fetch as fetch from test_utils import CharmTestCase @@ -27,6 +28,7 @@ TO_PATCH = [ 'relation_ids', 'related_units', 'cmp_pkgrevno', + 'arch', 'socket', 'unit_public_ip', 'determine_api_port', @@ -42,6 +44,7 @@ class HAProxyContextTests(CharmTestCase): self.relation_get.side_effect = self.test_relation.get self.config.side_effect = self.test_config.get self.cmp_pkgrevno.return_value = 1 + self.arch.return_value = 'amd64' @patch('charmhelpers.contrib.openstack.context.get_relation_ip') @patch('charmhelpers.contrib.openstack.context.mkdir') @@ -356,7 +359,8 @@ class MonContextTest(CharmTestCase): super(MonContextTest, self).setUp(context, TO_PATCH) self.config.side_effect = self.test_config.get self.unit_public_ip.return_value = '10.255.255.255' - self.cmp_pkgrevno.return_value = 1 + self.cmp_pkgrevno.side_effect = lambda *args: 1 + self.arch.return_value = 'amd64' @patch.object(ceph, 'config', lambda *args: '{"client.radosgw.gateway": {"rgw init timeout": 60}}') @@ -395,6 +399,7 @@ class MonContextTest(CharmTestCase): 'rgw_zone': 'default', 'fsid': 'testfsid', 'rgw_swift_versioning': False, + 'frontend': 'beast', } self.assertEqual(expect, mon_ctxt()) self.assertFalse(mock_ensure_rsv_v6.called) @@ -444,6 +449,7 @@ class MonContextTest(CharmTestCase): 'rgw_zone': 'default', 'fsid': 'testfsid', 'rgw_swift_versioning': False, + 'frontend': 'beast', } self.assertEqual(expect, mon_ctxt()) self.assertFalse(mock_ensure_rsv_v6.called) @@ -502,6 +508,7 @@ class MonContextTest(CharmTestCase): 'rgw_zone': 'default', 'fsid': 'testfsid', 'rgw_swift_versioning': False, + 'frontend': 'beast', } self.assertEqual(expect, mon_ctxt()) @@ -542,9 +549,68 @@ class MonContextTest(CharmTestCase): 'rgw_zone': 'default', 'fsid': 'testfsid', 'rgw_swift_versioning': False, + 'frontend': 'beast', } self.assertEqual(expect, mon_ctxt()) + def test_resolve_http_frontend(self): + _test_version = '12.2.0' + + def _compare_version(package, version): + return fetch.apt_pkg.version_compare( + _test_version, version + ) + + # Older releases, default and invalid configuration + self.cmp_pkgrevno.side_effect = _compare_version + self.assertEqual('civetweb', context.resolve_http_frontend()) + + # Default for Octopus but not Pacific + _test_version = '15.2.0' + self.assertEqual('beast', context.resolve_http_frontend()) + + self.arch.return_value = 's390x' + self.assertEqual('civetweb', context.resolve_http_frontend()) + + # Default for Pacific and later + _test_version = '16.2.0' + self.assertEqual('beast', context.resolve_http_frontend()) + self.arch.return_value = 'amd64' + self.assertEqual('beast', context.resolve_http_frontend()) + + def test_validate_http_frontend(self): + _test_version = '12.2.0' + + def _compare_version(package, version): + return fetch.apt_pkg.version_compare( + _test_version, version + ) + + self.cmp_pkgrevno.side_effect = _compare_version + + # Invalid configuration option + with self.assertRaises(ValueError): + context.validate_http_frontend('foobar') + + # beast config but ceph pre mimic + with self.assertRaises(ValueError): + context.validate_http_frontend('beast') + + # Mimic with valid configuration + _test_version = '13.2.0' + context.validate_http_frontend('beast') + context.validate_http_frontend('civetweb') + + # beast config on unsupported s390x/octopus + _test_version = '15.2.0' + self.arch.return_value = 's390x' + with self.assertRaises(ValueError): + context.validate_http_frontend('beast') + + # beast config on s390x/pacific + _test_version = '16.2.0' + context.validate_http_frontend('beast') + class ApacheContextTest(CharmTestCase):