
Signed-off-by: Jiri Podivin <jpodivin@redhat.com> Change-Id: I032f8a463b554154d61a36a12ee6b136d0196224
264 lines
9.4 KiB
Python
264 lines
9.4 KiB
Python
#!/usr/bin/env python
|
|
# Copyright 2019 Red Hat, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
"""ceph_pools_pg_protection module
|
|
Used by the ceph-pg validation.
|
|
"""
|
|
|
|
import six
|
|
|
|
from yaml import safe_load as yaml_safe_load
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
|
ANSIBLE_METADATA = {
|
|
'metadata_version': '0.1',
|
|
'status': ['preview'],
|
|
'supported_by': 'community'
|
|
}
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: ceph_pools_pg_protection
|
|
short_description: Warn if Ceph will not create CephPools based on PG and OSD numbers
|
|
description:
|
|
- The Ceph PG overdose protection check (https://ceph.com/community/new-luminous-pg-overdose-protection)
|
|
is executed by Ceph before a pool is created.
|
|
If the check does not pass, then the pool is not created.
|
|
- When TripleO deploys Ceph it triggers ceph-ansible which creates the pools that OpenStack needs.
|
|
- This validation runs the same check that the overdose protection uses
|
|
to determine if the user should update their CephPools, PG count, or number of OSDs.
|
|
Without this check a deployer may have to wait until after Ceph is running,
|
|
but before the pools are created to realize the deployment will fail.
|
|
- Used by the ceph-pg validation.
|
|
- Owned by the "DFG:Storage Squad:Ceph"
|
|
options:
|
|
num_osds:
|
|
description:
|
|
- The number of Ceph OSDs expected to be running during Pool creation.
|
|
- TripleO does not have this parameter
|
|
- In theory you can derive this parameter from TripleO parameters
|
|
required: True
|
|
type: int
|
|
ceph_pool_default_size:
|
|
description:
|
|
- The same as the TripleO CephPoolDefaultSize parameter
|
|
- Number of replicas of the data
|
|
required: False
|
|
default: 3
|
|
type: int
|
|
ceph_pool_default_pg_num:
|
|
description:
|
|
- The same as the TripleO CephPoolDefaultPgNum parameter
|
|
- The default number of Placement Groups a pool should have
|
|
- Ceph defaults this number to 16
|
|
- TripleO defaults this number to 128
|
|
required: False
|
|
default: 128
|
|
type: int
|
|
ceph_pools:
|
|
description:
|
|
- The same as the TripleO CephPools parameter
|
|
- A list of dictionaries
|
|
- Each embedded dict must have a name parameter
|
|
- Optional pg_num and size parameters may be set per pool
|
|
required: True
|
|
type: list
|
|
author:
|
|
- John Fulton (fultonj)
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
# Call this module from TripleO Ansible Validations
|
|
|
|
- name: Is the CephPools parameter configured correctly?
|
|
ceph_pools_pg_protection:
|
|
num_osds: 36
|
|
ceph_pool_default_size: 3
|
|
ceph_pool_default_pg_num: 128
|
|
ceph_pools:
|
|
- {"name": volumes, "pg_num": 1024,"pgp_num": 1024, "application": rbd, "size": 3}
|
|
- {"name": vms, "pg_num": 512, "pgp_num": 512, "application": rbd, "size": 3}
|
|
- {"name": images, "pg_num": 128, "pgp_num": 128, "application": rbd, "size": 3}
|
|
register: pool_creation_simulation
|
|
- name: Fail if CephPools parameter is not configured correctly
|
|
fail:
|
|
msg: pool_creation_simulation["message"]
|
|
when: not pool_creation_simulation["valid_input"]
|
|
|
|
# Call this module from within TripleO Heat Templates (if only num_osds was derived)
|
|
- name: Is the CephPools parameter configured correctly?
|
|
ceph_pools_pg_protection:
|
|
num_osds: 36
|
|
ceph_pool_default_size: {get_param: CephPoolDefaultSize}
|
|
ceph_pool_default_pg_num: {get_param: CephPoolDefaultPgNum}
|
|
ceph_pools: {get_param: CephPools}
|
|
register: pool_creation_simulation
|
|
|
|
'''
|
|
|
|
RETURN = '''
|
|
message:
|
|
description: A description of why Ceph might refuse to create the requested CephPools
|
|
type: str
|
|
returned: always
|
|
valid_input:
|
|
description: True only if Ceph would create all requested pools
|
|
type: boolean
|
|
returned: always
|
|
'''
|
|
|
|
|
|
def check_pg_num(pool, pg_num, size, num_osds=0, max_pgs_per_osd=200, pools={}):
|
|
"""
|
|
Returns empty string only if the Pool PG numbers are correct for the OSDs.
|
|
Otherwise returns an error message like the one Ceph would return.
|
|
"""
|
|
# The original check in C++ from the Ceph source code is:
|
|
#
|
|
# int OSDMonitor::check_pg_num(int64_t pool, int pg_num, int size, ostream *ss)
|
|
# {
|
|
# auto max_pgs_per_osd = g_conf->get_val<uint64_t>("mon_max_pg_per_osd");
|
|
# auto num_osds = std::max(osdmap.get_num_in_osds(), 3u); // assume min cluster size 3
|
|
# auto max_pgs = max_pgs_per_osd * num_osds;
|
|
# uint64_t projected = 0;
|
|
# if (pool < 0) {
|
|
# projected += pg_num * size;
|
|
# }
|
|
# for (const auto& i : osdmap.get_pools()) {
|
|
# if (i.first == pool) {
|
|
# projected += pg_num * size;
|
|
# } else {
|
|
# projected += i.second.get_pg_num() * i.second.get_size();
|
|
# }
|
|
# }
|
|
# if (projected > max_pgs) {
|
|
# if (pool >= 0) {
|
|
# *ss << "pool id " << pool;
|
|
# }
|
|
# *ss << " pg_num " << pg_num << " size " << size
|
|
# << " would mean " << projected
|
|
# << " total pgs, which exceeds max " << max_pgs
|
|
# << " (mon_max_pg_per_osd " << max_pgs_per_osd
|
|
# << " * num_in_osds " << num_osds << ")";
|
|
# return -ERANGE;
|
|
# }
|
|
# return 0;
|
|
# }
|
|
msg = ""
|
|
max_pgs = max_pgs_per_osd * num_osds
|
|
projected = 0
|
|
if len(pool) < 0:
|
|
projected = projected + (pg_num * size)
|
|
for pool_name, pool_sizes in six.iteritems(pools):
|
|
if pool_name == pool:
|
|
projected = projected + (pg_num * size)
|
|
else:
|
|
projected = projected + (int(pool_sizes['pg_num']) * int(pool_sizes['size']))
|
|
if projected > max_pgs:
|
|
msg = "Cannot add pool: " + str(pool) + \
|
|
" pg_num " + str(pg_num) + " size " + str(size) + \
|
|
" would mean " + str(projected) + \
|
|
" total pgs, which exceeds max " + str(max_pgs) + \
|
|
" (mon_max_pg_per_osd " + str(max_pgs_per_osd) + \
|
|
" * num_in_osds " + str(num_osds) + ")"
|
|
return msg
|
|
|
|
|
|
def simulate_pool_creation(num_osds, ceph_pools,
|
|
ceph_pool_default_size=3,
|
|
ceph_pool_default_pg_num=128,
|
|
max_pgs_per_osd=200):
|
|
"""
|
|
Simulate ceph-ansible asking Ceph to create the pools in the ceph_pools list
|
|
"""
|
|
msg = ""
|
|
failed = False
|
|
created_pools = {}
|
|
for pool in ceph_pools:
|
|
if 'size' not in pool:
|
|
pool['size'] = ceph_pool_default_size
|
|
if 'pg_num' not in pool:
|
|
pool['pg_num'] = ceph_pool_default_pg_num
|
|
ceph_msg = check_pg_num(pool['name'], pool['pg_num'], pool['size'],
|
|
num_osds, max_pgs_per_osd, created_pools)
|
|
if len(ceph_msg) == 0:
|
|
created_pools[pool['name']] = {'pg_num': pool['pg_num'], 'size': pool['size']}
|
|
else:
|
|
failed = True
|
|
break
|
|
if failed:
|
|
msg = "The following Ceph pools would be created (but no others):" + \
|
|
"\n" + str(created_pools) + "\n" + \
|
|
"Pool creation would then fail with the following from Ceph:" + \
|
|
"\n" + ceph_msg + "\n" + \
|
|
"Please use https://ceph.io/pgcalc and then update the CephPools parameter"
|
|
simulation_results = {}
|
|
simulation_results['failed'] = failed
|
|
simulation_results['msg'] = msg
|
|
return simulation_results
|
|
|
|
|
|
def run_module():
|
|
"""This module never changes state of a target system, it only
|
|
evaluates if inputs will work when Ceph processes then.
|
|
There shouldn't be anything like the following
|
|
result['changed'] = True
|
|
|
|
This module does not currently have fail options. It should
|
|
only evaluate input and make result of the evaluation available.
|
|
So it doesn't currently do anything like the following by design.
|
|
module.fail_json(msg='Failing for invalid input', **result)
|
|
|
|
Exit and pass the key/value results of the simulation
|
|
"""
|
|
# Seed the result dict in the object
|
|
result = dict(
|
|
changed=False,
|
|
valid_input=True,
|
|
message=''
|
|
)
|
|
|
|
# Use AnsibleModule object abstraction to work with Ansible
|
|
module = AnsibleModule(
|
|
argument_spec=yaml_safe_load(DOCUMENTATION)['options'],
|
|
supports_check_mode=False
|
|
)
|
|
|
|
# Check mode not supported
|
|
if module.check_mode:
|
|
module.exit_json(**result)
|
|
|
|
# Simulate Ceph pool creation
|
|
simulation = simulate_pool_creation(module.params['num_osds'],
|
|
module.params['ceph_pools'],
|
|
module.params['ceph_pool_default_size'],
|
|
module.params['ceph_pool_default_pg_num'])
|
|
if simulation['failed']:
|
|
result['message'] = "Invalid Ceph configuration: " + simulation['msg']
|
|
result['valid_input'] = False
|
|
else:
|
|
result['message'] = 'Provided CephPools satisfy PG overdose protection'
|
|
result['valid_input'] = True
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
def main():
|
|
run_module()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|