Add Ceph BlueStore Compression support
Func-Test-Pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/398 Change-Id: I997bb15c692c442db2db79d96882a2204186dbe1
This commit is contained in:
parent
a5698e04be
commit
8f3dfb184b
|
@ -3245,6 +3245,18 @@ class CephBlueStoreCompressionContext(OSContextGenerator):
|
||||||
"""
|
"""
|
||||||
return self.op
|
return self.op
|
||||||
|
|
||||||
|
def get_kwargs(self):
|
||||||
|
"""Get values for use as keyword arguments.
|
||||||
|
|
||||||
|
:returns: Context values with key suitable for use as kwargs to
|
||||||
|
CephBrokerRq add_op_create_*_pool methods.
|
||||||
|
:rtype: Dict[str,any]
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
k.replace('-', '_'): v
|
||||||
|
for k, v in self.op.items()
|
||||||
|
}
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
"""Validate options.
|
"""Validate options.
|
||||||
|
|
||||||
|
|
|
@ -705,12 +705,12 @@ class ErasurePool(BasePool):
|
||||||
# from different handling of this in the `charms.ceph` library.
|
# from different handling of this in the `charms.ceph` library.
|
||||||
self.erasure_code_profile = op.get('erasure-profile',
|
self.erasure_code_profile = op.get('erasure-profile',
|
||||||
'default-canonical')
|
'default-canonical')
|
||||||
|
self.allow_ec_overwrites = op.get('allow-ec-overwrites')
|
||||||
else:
|
else:
|
||||||
# We keep the class default when initialized from keyword arguments
|
# We keep the class default when initialized from keyword arguments
|
||||||
# to not break the API for any other consumers.
|
# to not break the API for any other consumers.
|
||||||
self.erasure_code_profile = erasure_code_profile or 'default'
|
self.erasure_code_profile = erasure_code_profile or 'default'
|
||||||
|
self.allow_ec_overwrites = allow_ec_overwrites
|
||||||
self.allow_ec_overwrites = allow_ec_overwrites
|
|
||||||
|
|
||||||
def _create(self):
|
def _create(self):
|
||||||
# Try to find the erasure profile information in order to properly
|
# Try to find the erasure profile information in order to properly
|
||||||
|
@ -1972,12 +1972,14 @@ class CephBrokerRq(object):
|
||||||
'request-id': self.request_id})
|
'request-id': self.request_id})
|
||||||
|
|
||||||
def _ops_equal(self, other):
|
def _ops_equal(self, other):
|
||||||
|
keys_to_compare = [
|
||||||
|
'replicas', 'name', 'op', 'pg_num', 'group-permission',
|
||||||
|
'object-prefix-permissions',
|
||||||
|
]
|
||||||
|
keys_to_compare += list(self._partial_build_common_op_create().keys())
|
||||||
if len(self.ops) == len(other.ops):
|
if len(self.ops) == len(other.ops):
|
||||||
for req_no in range(0, len(self.ops)):
|
for req_no in range(0, len(self.ops)):
|
||||||
for key in [
|
for key in keys_to_compare:
|
||||||
'replicas', 'name', 'op', 'pg_num', 'weight',
|
|
||||||
'group', 'group-namespace', 'group-permission',
|
|
||||||
'object-prefix-permissions']:
|
|
||||||
if self.ops[req_no].get(key) != other.ops[req_no].get(key):
|
if self.ops[req_no].get(key) != other.ops[req_no].get(key):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|
66
config.yaml
66
config.yaml
|
@ -459,3 +459,69 @@ options:
|
||||||
override YAML files in the service's policy.d directory. The resource
|
override YAML files in the service's policy.d directory. The resource
|
||||||
file should be a ZIP file containing at least one yaml file with a .yaml
|
file should be a ZIP file containing at least one yaml file with a .yaml
|
||||||
or .yml extension. If False then remove the overrides.
|
or .yml extension. If False then remove the overrides.
|
||||||
|
bluestore-compression-algorithm:
|
||||||
|
type: string
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
Compressor to use (if any) for pools requested by this charm.
|
||||||
|
.
|
||||||
|
NOTE: The ceph-osd charm sets a global default for this value (defaults
|
||||||
|
to 'lz4' unless configured by the end user) which will be used unless
|
||||||
|
specified for individual pools.
|
||||||
|
bluestore-compression-mode:
|
||||||
|
type: string
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
Policy for using compression on pools requested by this charm.
|
||||||
|
.
|
||||||
|
'none' means never use compression.
|
||||||
|
'passive' means use compression when clients hint that data is
|
||||||
|
compressible.
|
||||||
|
'aggressive' means use compression unless clients hint that
|
||||||
|
data is not compressible.
|
||||||
|
'force' means use compression under all circumstances even if the clients
|
||||||
|
hint that the data is not compressible.
|
||||||
|
bluestore-compression-required-ratio:
|
||||||
|
type: float
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
The ratio of the size of the data chunk after compression relative to the
|
||||||
|
original size must be at least this small in order to store the
|
||||||
|
compressed version on pools requested by this charm.
|
||||||
|
bluestore-compression-min-blob-size:
|
||||||
|
type: int
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
Chunks smaller than this are never compressed on pools requested by
|
||||||
|
this charm.
|
||||||
|
bluestore-compression-min-blob-size-hdd:
|
||||||
|
type: int
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
Value of bluestore compression min blob size for rotational media on
|
||||||
|
pools requested by this charm.
|
||||||
|
bluestore-compression-min-blob-size-ssd:
|
||||||
|
type: int
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
Value of bluestore compression min blob size for solid state media on
|
||||||
|
pools requested by this charm.
|
||||||
|
bluestore-compression-max-blob-size:
|
||||||
|
type: int
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
Chunks larger than this are broken into smaller blobs sizing bluestore
|
||||||
|
compression max blob size before being compressed on pools requested by
|
||||||
|
this charm.
|
||||||
|
bluestore-compression-max-blob-size-hdd:
|
||||||
|
type: int
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
Value of bluestore compression max blob size for rotational media on
|
||||||
|
pools requested by this charm.
|
||||||
|
bluestore-compression-max-blob-size-ssd:
|
||||||
|
type: int
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
Value of bluestore compression max blob size for solid state media on
|
||||||
|
pools requested by this charm.
|
||||||
|
|
|
@ -126,7 +126,8 @@ from charmhelpers.contrib.openstack.ip import (
|
||||||
PUBLIC, INTERNAL, ADMIN
|
PUBLIC, INTERNAL, ADMIN
|
||||||
)
|
)
|
||||||
from charmhelpers.contrib.openstack.context import (
|
from charmhelpers.contrib.openstack.context import (
|
||||||
ADDRESS_TYPES
|
ADDRESS_TYPES,
|
||||||
|
CephBlueStoreCompressionContext,
|
||||||
)
|
)
|
||||||
from charmhelpers.contrib.charmsupport import nrpe
|
from charmhelpers.contrib.charmsupport import nrpe
|
||||||
from charmhelpers.contrib.hardening.harden import harden
|
from charmhelpers.contrib.hardening.harden import harden
|
||||||
|
@ -310,6 +311,7 @@ def get_ceph_request():
|
||||||
rq = CephBrokerRq()
|
rq = CephBrokerRq()
|
||||||
weight = config('ceph-pool-weight')
|
weight = config('ceph-pool-weight')
|
||||||
replicas = config('ceph-osd-replication-count')
|
replicas = config('ceph-osd-replication-count')
|
||||||
|
bluestore_compression = CephBlueStoreCompressionContext()
|
||||||
|
|
||||||
if config('pool-type') == 'erasure-coded':
|
if config('pool-type') == 'erasure-coded':
|
||||||
# General EC plugin config
|
# General EC plugin config
|
||||||
|
@ -361,17 +363,35 @@ def get_ceph_request():
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create EC data pool
|
# Create EC data pool
|
||||||
rq.add_op_create_erasure_pool(
|
|
||||||
name=pool_name,
|
# NOTE(fnordahl): once we deprecate Python 3.5 support we can do
|
||||||
erasure_profile=profile_name,
|
# the unpacking of the BlueStore compression arguments as part of
|
||||||
weight=weight,
|
# the function arguments. Until then we need to build the dict
|
||||||
group="images",
|
# prior to the function call.
|
||||||
app_name="rbd",
|
kwargs = {
|
||||||
allow_ec_overwrites=True
|
'name': pool_name,
|
||||||
)
|
'erasure_profile': profile_name,
|
||||||
|
'weight': weight,
|
||||||
|
'group': "images",
|
||||||
|
'app_name': "rbd",
|
||||||
|
'allow_ec_overwrites': True,
|
||||||
|
}
|
||||||
|
kwargs.update(bluestore_compression.get_kwargs())
|
||||||
|
rq.add_op_create_erasure_pool(**kwargs)
|
||||||
else:
|
else:
|
||||||
rq.add_op_create_pool(name=pool_name, replica_count=replicas,
|
# NOTE(fnordahl): once we deprecate Python 3.5 support we can do
|
||||||
weight=weight, group='images', app_name='rbd')
|
# the unpacking of the BlueStore compression arguments as part of
|
||||||
|
# the function arguments. Until then we need to build the dict
|
||||||
|
# prior to the function call.
|
||||||
|
kwargs = {
|
||||||
|
'name': pool_name,
|
||||||
|
'replica_count': replicas,
|
||||||
|
'weight': weight,
|
||||||
|
'group': 'images',
|
||||||
|
'app_name': 'rbd',
|
||||||
|
}
|
||||||
|
kwargs.update(bluestore_compression.get_kwargs())
|
||||||
|
rq.add_op_create_replicated_pool(**kwargs)
|
||||||
|
|
||||||
if config('restrict-ceph-pools'):
|
if config('restrict-ceph-pools'):
|
||||||
rq.add_op_request_access_to_group(
|
rq.add_op_request_access_to_group(
|
||||||
|
@ -498,8 +518,18 @@ def config_changed():
|
||||||
|
|
||||||
# NOTE(jamespage): trigger any configuration related changes
|
# NOTE(jamespage): trigger any configuration related changes
|
||||||
# for cephx permissions restrictions
|
# for cephx permissions restrictions
|
||||||
ceph_changed()
|
try:
|
||||||
update_image_location_policy(CONFIGS)
|
ceph_changed()
|
||||||
|
update_image_location_policy(CONFIGS)
|
||||||
|
except ValueError as e:
|
||||||
|
# The end user has most likely provided a invalid value for a
|
||||||
|
# configuration option. Just log the traceback here, the end user will
|
||||||
|
# be notified by assess_status() called at the end of the hook
|
||||||
|
# execution.
|
||||||
|
juju_log(
|
||||||
|
'Caught ValueError, invalid value provided for configuration?: '
|
||||||
|
'"{}"'.format(str(e)),
|
||||||
|
level=DEBUG)
|
||||||
|
|
||||||
# call the policy overrides handler which will install any policy overrides
|
# call the policy overrides handler which will install any policy overrides
|
||||||
maybe_do_policyd_overrides_on_config_changed(
|
maybe_do_policyd_overrides_on_config_changed(
|
||||||
|
|
|
@ -462,9 +462,8 @@ def get_optional_interfaces():
|
||||||
return optional_interfaces
|
return optional_interfaces
|
||||||
|
|
||||||
|
|
||||||
def check_optional_relations(configs):
|
def check_optional_config_and_relations(configs):
|
||||||
"""Check that if we have a relation_id for high availability that we can
|
"""Validate optional configuration and relations when present.
|
||||||
get the hacluster config. If we can't then we are blocked.
|
|
||||||
|
|
||||||
This function is called from assess_status/set_os_workload_status as the
|
This function is called from assess_status/set_os_workload_status as the
|
||||||
charm_func and needs to return either None, None if there is no problem or
|
charm_func and needs to return either None, None if there is no problem or
|
||||||
|
@ -473,6 +472,8 @@ def check_optional_relations(configs):
|
||||||
:param configs: an OSConfigRender() instance.
|
:param configs: an OSConfigRender() instance.
|
||||||
:return 2-tuple: (string, string) = (status, message)
|
:return 2-tuple: (string, string) = (status, message)
|
||||||
"""
|
"""
|
||||||
|
# Check that if we have a relation_id for high availability that we can
|
||||||
|
# get the hacluster config. If we can't then we are blocked.
|
||||||
if relation_ids('ha'):
|
if relation_ids('ha'):
|
||||||
try:
|
try:
|
||||||
get_hacluster_config()
|
get_hacluster_config()
|
||||||
|
@ -480,6 +481,19 @@ def check_optional_relations(configs):
|
||||||
return ('blocked',
|
return ('blocked',
|
||||||
'hacluster missing configuration: '
|
'hacluster missing configuration: '
|
||||||
'vip, vip_iface, vip_cidr')
|
'vip, vip_iface, vip_cidr')
|
||||||
|
|
||||||
|
if relation_ids('ceph'):
|
||||||
|
# Check that provided Ceph BlueStoe configuration is valid.
|
||||||
|
try:
|
||||||
|
bluestore_compression = context.CephBlueStoreCompressionContext()
|
||||||
|
bluestore_compression.validate()
|
||||||
|
except AttributeError:
|
||||||
|
# The charm does late installation of the `ceph-common` package and
|
||||||
|
# the class initializer above will throw an exception until it is.
|
||||||
|
pass
|
||||||
|
except ValueError as e:
|
||||||
|
return ('blocked', 'Invalid configuration: {}'.format(str(e)))
|
||||||
|
|
||||||
# return 'unknown' as the lowest priority to not clobber an existing
|
# return 'unknown' as the lowest priority to not clobber an existing
|
||||||
# status.
|
# status.
|
||||||
return "unknown", ""
|
return "unknown", ""
|
||||||
|
@ -522,7 +536,7 @@ def assess_status_func(configs):
|
||||||
_services, _ = get_managed_services_and_ports(services(), [])
|
_services, _ = get_managed_services_and_ports(services(), [])
|
||||||
return make_assess_status_func(
|
return make_assess_status_func(
|
||||||
configs, required_interfaces,
|
configs, required_interfaces,
|
||||||
charm_func=check_optional_relations,
|
charm_func=check_optional_config_and_relations,
|
||||||
services=_services, ports=None)
|
services=_services, ports=None)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
# requirements. They are intertwined. Also, Zaza itself should specify
|
# requirements. They are intertwined. Also, Zaza itself should specify
|
||||||
# all of its own requirements and if it doesn't, fix it there.
|
# all of its own requirements and if it doesn't, fix it there.
|
||||||
#
|
#
|
||||||
setuptools<50.0.0 # https://github.com/pypa/setuptools/commit/04e3df22df840c6bb244e9b27bc56750c44b7c85
|
|
||||||
charm-tools>=2.4.4
|
charm-tools>=2.4.4
|
||||||
requests>=2.18.4
|
requests>=2.18.4
|
||||||
mock>=1.2
|
mock>=1.2
|
||||||
|
|
|
@ -39,6 +39,7 @@ tests:
|
||||||
- zaza.openstack.charm_tests.glance.tests.GlanceTest
|
- zaza.openstack.charm_tests.glance.tests.GlanceTest
|
||||||
- zaza.openstack.charm_tests.policyd.tests.GlanceTests
|
- zaza.openstack.charm_tests.policyd.tests.GlanceTests
|
||||||
- zaza.openstack.charm_tests.ceph.tests.CheckPoolTypes
|
- zaza.openstack.charm_tests.ceph.tests.CheckPoolTypes
|
||||||
|
- zaza.openstack.charm_tests.ceph.tests.BlueStoreCompressionCharmOperation
|
||||||
- zaza.openstack.charm_tests.glance.tests.GlanceTest
|
- zaza.openstack.charm_tests.glance.tests.GlanceTest
|
||||||
- zaza.openstack.charm_tests.policyd.tests.GlanceTests
|
- zaza.openstack.charm_tests.policyd.tests.GlanceTests
|
||||||
- full_run:
|
- full_run:
|
||||||
|
|
|
@ -354,12 +354,14 @@ class GlanceRelationTests(CharmTestCase):
|
||||||
for c in [call('/etc/glance/glance.conf')]:
|
for c in [call('/etc/glance/glance.conf')]:
|
||||||
self.assertNotIn(c, configs.write.call_args_list)
|
self.assertNotIn(c, configs.write.call_args_list)
|
||||||
|
|
||||||
|
@patch.object(relations, 'CephBlueStoreCompressionContext')
|
||||||
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
||||||
'.add_op_request_access_to_group')
|
'.add_op_request_access_to_group')
|
||||||
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
||||||
'.add_op_create_pool')
|
'.add_op_create_replicated_pool')
|
||||||
def test_create_pool_op(self, mock_create_pool,
|
def test_create_pool_op(self, mock_create_pool,
|
||||||
mock_request_access):
|
mock_request_access,
|
||||||
|
mock_bluestore_compression):
|
||||||
self.service_name.return_value = 'glance'
|
self.service_name.return_value = 'glance'
|
||||||
self.test_config.set('ceph-osd-replication-count', 3)
|
self.test_config.set('ceph-osd-replication-count', 3)
|
||||||
self.test_config.set('ceph-pool-weight', 6)
|
self.test_config.set('ceph-pool-weight', 6)
|
||||||
|
@ -384,6 +386,21 @@ class GlanceRelationTests(CharmTestCase):
|
||||||
permission='rwx'),
|
permission='rwx'),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
# confirm operation with bluestore compression
|
||||||
|
mock_create_pool.reset_mock()
|
||||||
|
mock_bluestore_compression().get_kwargs.return_value = {
|
||||||
|
'compression_mode': 'fake',
|
||||||
|
}
|
||||||
|
relations.get_ceph_request()
|
||||||
|
mock_create_pool.assert_called_once_with(
|
||||||
|
name='glance',
|
||||||
|
replica_count=3,
|
||||||
|
weight=6,
|
||||||
|
group='images',
|
||||||
|
app_name='rbd',
|
||||||
|
compression_mode='fake')
|
||||||
|
|
||||||
|
@patch.object(relations, 'CephBlueStoreCompressionContext')
|
||||||
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
||||||
'.add_op_create_erasure_pool')
|
'.add_op_create_erasure_pool')
|
||||||
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
||||||
|
@ -395,7 +412,8 @@ class GlanceRelationTests(CharmTestCase):
|
||||||
def test_create_ec_pool_op(self, mock_create_pool,
|
def test_create_ec_pool_op(self, mock_create_pool,
|
||||||
mock_request_access,
|
mock_request_access,
|
||||||
mock_create_erasure_profile,
|
mock_create_erasure_profile,
|
||||||
mock_create_erasure_pool):
|
mock_create_erasure_pool,
|
||||||
|
mock_bluestore_compression):
|
||||||
self.service_name.return_value = 'glance'
|
self.service_name.return_value = 'glance'
|
||||||
self.test_config.set('ceph-osd-replication-count', 3)
|
self.test_config.set('ceph-osd-replication-count', 3)
|
||||||
self.test_config.set('ceph-pool-weight', 6)
|
self.test_config.set('ceph-pool-weight', 6)
|
||||||
|
@ -431,6 +449,21 @@ class GlanceRelationTests(CharmTestCase):
|
||||||
allow_ec_overwrites=True)
|
allow_ec_overwrites=True)
|
||||||
mock_request_access.assert_not_called()
|
mock_request_access.assert_not_called()
|
||||||
|
|
||||||
|
# confirm operation with bluestore compression
|
||||||
|
mock_create_erasure_pool.reset_mock()
|
||||||
|
mock_bluestore_compression().get_kwargs.return_value = {
|
||||||
|
'compression_mode': 'fake',
|
||||||
|
}
|
||||||
|
relations.get_ceph_request()
|
||||||
|
mock_create_erasure_pool.assert_called_once_with(
|
||||||
|
name='glance',
|
||||||
|
erasure_profile='glance-profile',
|
||||||
|
weight=5.94,
|
||||||
|
group='images',
|
||||||
|
app_name='rbd',
|
||||||
|
allow_ec_overwrites=True,
|
||||||
|
compression_mode='fake')
|
||||||
|
|
||||||
@patch.object(relations, 'get_ceph_request')
|
@patch.object(relations, 'get_ceph_request')
|
||||||
@patch.object(relations, 'send_request_if_needed')
|
@patch.object(relations, 'send_request_if_needed')
|
||||||
@patch.object(relations, 'is_request_complete')
|
@patch.object(relations, 'is_request_complete')
|
||||||
|
|
|
@ -309,7 +309,7 @@ class TestGlanceUtils(CharmTestCase):
|
||||||
make_assess_status_func.assert_called_once_with(
|
make_assess_status_func.assert_called_once_with(
|
||||||
'test-config',
|
'test-config',
|
||||||
{'int': ['test 1'], 'opt': ['test 2']},
|
{'int': ['test 1'], 'opt': ['test 2']},
|
||||||
charm_func=utils.check_optional_relations,
|
charm_func=utils.check_optional_config_and_relations,
|
||||||
services=['s1'], ports=None)
|
services=['s1'], ports=None)
|
||||||
|
|
||||||
def test_pause_unit_helper(self):
|
def test_pause_unit_helper(self):
|
||||||
|
|
Loading…
Reference in New Issue