diff --git a/actions/create_pool.py b/actions/create_pool.py index 40686c2f..f8faee1f 100755 --- a/actions/create_pool.py +++ b/actions/create_pool.py @@ -30,10 +30,12 @@ def create_pool(): try: if pool_type == "replicated": replicas = action_get("replicas") + crush_profile_name = action_get("profile-name") replicated_pool = ReplicatedPool(name=pool_name, service='admin', replicas=replicas, app_name=app_name, + profile_name=crush_profile_name, percent_data=float(percent_data), ) replicated_pool.create() diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/hooks/charmhelpers/contrib/storage/linux/ceph.py index 9a34e4b0..369699fd 100644 --- a/hooks/charmhelpers/contrib/storage/linux/ceph.py +++ b/hooks/charmhelpers/contrib/storage/linux/ceph.py @@ -615,7 +615,8 @@ class Pool(BasePool): class ReplicatedPool(BasePool): def __init__(self, service, name=None, pg_num=None, replicas=None, - percent_data=None, app_name=None, op=None): + percent_data=None, app_name=None, op=None, + profile_name='replicated_rule'): """Initialize ReplicatedPool object. Pool information is either initialized from individual keyword @@ -632,6 +633,8 @@ class ReplicatedPool(BasePool): to this replicated pool. :type replicas: int :raises: KeyError + :param profile_name: Crush Profile to use + :type profile_name: Optional[str] """ # NOTE: Do not perform initialization steps that require live data from # a running cluster here. The *Pool classes may be used for validation. @@ -646,11 +649,20 @@ class ReplicatedPool(BasePool): # we will fail with KeyError if it is not provided. self.replicas = op['replicas'] self.pg_num = op.get('pg_num') + self.profile_name = op.get('crush-profile', profile_name) else: self.replicas = replicas or 2 self.pg_num = pg_num + self.profile_name = profile_name or 'replicated_rule' def _create(self): + # Validate if crush profile exists + if self.profile_name is None: + msg = ("Failed to discover crush profile named " + "{}".format(self.profile_name)) + log(msg, level=ERROR) + raise PoolCreationError(msg) + # Do extra validation on pg_num with data from live cluster if self.pg_num: # Since the number of placement groups were specified, ensure @@ -668,12 +680,12 @@ class ReplicatedPool(BasePool): '--pg-num-min={}'.format( min(AUTOSCALER_DEFAULT_PGS, self.pg_num) ), - self.name, str(self.pg_num) + self.name, str(self.pg_num), self.profile_name ] else: cmd = [ 'ceph', '--id', self.service, 'osd', 'pool', 'create', - self.name, str(self.pg_num) + self.name, str(self.pg_num), self.profile_name ] check_call(cmd) @@ -692,7 +704,7 @@ class ErasurePool(BasePool): def __init__(self, service, name=None, erasure_code_profile=None, percent_data=None, app_name=None, op=None, allow_ec_overwrites=False): - """Initialize ReplicatedPool object. + """Initialize ErasurePool object. Pool information is either initialized from individual keyword arguments or from a individual CephBrokerRq operation Dict. @@ -1859,7 +1871,7 @@ class CephBrokerRq(object): } def add_op_create_replicated_pool(self, name, replica_count=3, pg_num=None, - **kwargs): + crush_profile=None, **kwargs): """Adds an operation to create a replicated pool. Refer to docstring for ``_partial_build_common_op_create`` for @@ -1873,6 +1885,10 @@ class CephBrokerRq(object): for pool. :type pg_num: int :raises: AssertionError if provided data is of invalid type/range + :param crush_profile: Name of crush profile to use. If not set the + ceph-mon unit handling the broker request will + set its default value. + :type crush_profile: Optional[str] """ if pg_num and kwargs.get('weight'): raise ValueError('pg_num and weight are mutually exclusive') @@ -1882,6 +1898,7 @@ class CephBrokerRq(object): 'name': name, 'replicas': replica_count, 'pg_num': pg_num, + 'crush-profile': crush_profile } op.update(self._partial_build_common_op_create(**kwargs)) diff --git a/unit_tests/test_ceph_ops.py b/unit_tests/test_ceph_ops.py index 000ddbad..25e095e4 100644 --- a/unit_tests/test_ceph_ops.py +++ b/unit_tests/test_ceph_ops.py @@ -66,6 +66,27 @@ class TestCephOps(unittest.TestCase): mock_delete_pool.assert_called_with(service='admin', name='foo') self.assertEqual(json.loads(rc), {'exit-code': 0}) + @patch('charmhelpers.contrib.storage.linux.ceph.cmp_pkgrevno') + @patch.object(broker, 'pool_exists') + @patch.object(broker.ReplicatedPool, 'create') + @patch.object(broker, 'log', lambda *args, **kwargs: None) + def test_process_requests_create_replicated_pool(self, + mock_replicated_pool, + mock_pool_exists, + mock_cmp_pkgrevno): + mock_pool_exists.return_value = False + mock_cmp_pkgrevno.return_value = 1 + reqs = json.dumps({'api-version': 1, + 'ops': [{ + 'op': 'create-pool', + 'name': 'foo', + 'replicas': 3 + }]}) + rc = broker.process_requests(reqs) + mock_pool_exists.assert_called_with(service='admin', name='foo') + mock_replicated_pool.assert_called_with() + self.assertEqual(json.loads(rc), {'exit-code': 0}) + @patch('charmhelpers.contrib.storage.linux.ceph.cmp_pkgrevno') @patch.object(broker, 'pool_exists') @patch.object(broker.ErasurePool, 'create')