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):