ceph: adding '--upgrade' option to external script

An '--upgrade' option is added to the option list, which upgrades the user
permissions (caps) to match newer version. Will try to detect
'rgw-pool-prefix' or user can provide it through the argument.

Segregated the command line arguments into three groups, common, output and
upgrade

Signed-off-by: Arun Kumar Mohan <amohan@redhat.com>
This commit is contained in:
Arun Kumar Mohan
2020-10-09 18:59:25 +05:30
parent cebcf0a04e
commit fbb8af12e1
@@ -49,39 +49,48 @@ class ExecutionFailureException(Exception):
class RadosJSON:
EXTERNAL_USER_NAME = "client.healthchecker"
EMPTY_OUTPUT_LIST = "Empty output list"
DEFAULT_RGW_POOL_PREFIX = "default"
@classmethod
def gen_arg_parser(cls, args_to_parse=None):
argP = argparse.ArgumentParser()
argP.add_argument("--verbose", "-v",
common_group = argP.add_argument_group('common')
common_group.add_argument("--verbose", "-v",
action='store_true', default=False)
argP.add_argument("--ceph-conf", "-c",
common_group.add_argument("--ceph-conf", "-c",
help="Provide a ceph conf file.", type=str)
argP.add_argument("--run-as-user", "-u",
help="Provides a user name to check the cluster's health status, must be prefixed by 'client.'",
default=cls.EXTERNAL_USER_NAME, type=str)
argP.add_argument("--format", "-t", choices=["json", "bash"],
default='json', help="Provides the output format (json | bash)")
argP.add_argument("--cluster-name", default="openshift-storage",
common_group.add_argument("--run-as-user", "-u", default="", type=str,
help="Provides a user name to check the cluster's health status, must be prefixed by 'client.'")
common_group.add_argument("--cluster-name", default="openshift-storage",
help="Ceph cluster name")
argP.add_argument("--output", "-o", default="",
help="Output will be stored into the provided file")
argP.add_argument("--cephfs-filesystem-name", default="",
help="Provides the name of the Ceph filesystem")
argP.add_argument("--cephfs-data-pool-name", default="",
help="Provides the name of the cephfs data pool")
argP.add_argument("--rbd-data-pool-name", default="", required=True,
help="Provides the name of the RBD datapool")
argP.add_argument("--namespace", default="",
common_group.add_argument("--namespace", default="",
help="Namespace where CephCluster is running")
argP.add_argument("--rgw-pool-prefix", default="default",
common_group.add_argument("--rgw-pool-prefix", default="",
help="RGW Pool prefix")
argP.add_argument("--rgw-endpoint", default="", required=False,
output_group = argP.add_argument_group('output')
output_group.add_argument("--format", "-t", choices=["json", "bash"],
default='json', help="Provides the output format (json | bash)")
output_group.add_argument("--output", "-o", default="",
help="Output will be stored into the provided file")
output_group.add_argument("--cephfs-filesystem-name", default="",
help="Provides the name of the Ceph filesystem")
output_group.add_argument("--cephfs-data-pool-name", default="",
help="Provides the name of the cephfs data pool")
output_group.add_argument("--rbd-data-pool-name", default="", required=False,
help="Provides the name of the RBD datapool")
output_group.add_argument("--rgw-endpoint", default="", required=False,
help="Rados GateWay endpoint (in <IP>:<PORT> format)")
argP.add_argument("--monitoring-endpoint", default="", required=False,
output_group.add_argument("--monitoring-endpoint", default="", required=False,
help="Ceph Manager prometheus exporter endpoints comma separated list of <IP> entries")
argP.add_argument("--monitoring-endpoint-port", default="9283", required=False,
output_group.add_argument("--monitoring-endpoint-port", default="9283", required=False,
help="Ceph Manager prometheus exporter port")
upgrade_group = argP.add_argument_group('upgrade')
upgrade_group.add_argument("--upgrade", action='store_true', default=False,
help="Upgrades the 'user' with all the permissions needed for the new cluster version")
if args_to_parse:
assert type(args_to_parse) == list, \
"Argument to 'gen_arg_parser' should be a list"
@@ -89,6 +98,18 @@ class RadosJSON:
args_to_parse = sys.argv[1:]
return argP.parse_args(args_to_parse)
def _check_conflicting_options(self):
if not self._arg_parser.upgrade and not self._arg_parser.rbd_data_pool_name:
raise ExecutionFailureException(
"Either '--upgrade' or '--rbd-data-pool-name <pool_name>' should be specified")
if self._arg_parser.upgrade and self._arg_parser.rbd_data_pool_name:
raise ExecutionFailureException(
"Both '--upgrade' and '--rbd-data-pool-name <pool_name>' should not be specified, choose only one")
# a user name must be provided while using '--upgrade' option
if not self._arg_parser.run_as_user and self._arg_parser.upgrade:
raise ExecutionFailureException(
"Please provide an existing user-name through '--run-as-user' (or '-u') flag while upgrading")
def _invalid_endpoint(self, endpoint_str):
try:
ipv4, port = endpoint_str.split(':')
@@ -132,11 +153,24 @@ class RadosJSON:
self.out_map = {}
self._excluded_keys = set()
self._arg_parser = self.gen_arg_parser(args_to_parse=arg_list)
self._check_conflicting_options()
self.run_as_user = self._arg_parser.run_as_user
self.output_file = self._arg_parser.output
self.ceph_conf = self._arg_parser.ceph_conf
self.run_as_user = self._arg_parser.run_as_user
if not self.run_as_user:
self.MIN_USER_CAP_PERMISSIONS = {
'mgr': 'allow command config',
'mon': 'allow r, allow command quorum_status, allow command version',
'osd': "allow rwx pool={0}.rgw.meta, " +
"allow r pool=.rgw.root, " +
"allow rw pool={0}.rgw.control, " +
"allow rx pool={0}.rgw.log, " +
"allow x pool={0}.rgw.buckets.index"
}
# if user not provided, give a default user
if not self.run_as_user and not self._arg_parser.upgrade:
self.run_as_user = self.EXTERNAL_USER_NAME
if not self._arg_parser.rgw_pool_prefix and not self._arg_parser.upgrade:
self._arg_parser.rgw_pool_prefix = self.DEFAULT_RGW_POOL_PREFIX
if self.ceph_conf:
self.cluster = rados.Rados(conffile=self.ceph_conf)
else:
@@ -159,7 +193,9 @@ class RadosJSON:
print("Return Val: {}\nCommand Output: {}\nError Message: {}\n----------\n".format(
ret_val, cmd_out, err_msg))
json_out = {}
if ret_val == 0:
# if there is no error (i.e; ret_val is ZERO) and 'cmd_out' is not empty
# then convert 'cmd_out' to a json output
if ret_val == 0 and cmd_out:
json_out = json.loads(cmd_out)
return ret_val, json_out, err_msg
@@ -351,13 +387,9 @@ class RadosJSON:
def create_checkerKey(self):
cmd_json = {"prefix": "auth get-or-create",
"entity": self.run_as_user,
"caps": ["mon", "allow r, allow command quorum_status, allow command version",
"mgr", "allow command config",
"osd", ("allow rwx pool={0}.rgw.meta, " +
"allow r pool=.rgw.root, " +
"allow rw pool={0}.rgw.control, " +
"allow rx pool={0}.rgw.log, " +
"allow x pool={0}.rgw.buckets.index").format(self._arg_parser.rgw_pool_prefix)],
"caps": ["mon", self.MIN_USER_CAP_PERMISSIONS['mon'],
"mgr", self.MIN_USER_CAP_PERMISSIONS['mgr'],
"osd", self.MIN_USER_CAP_PERMISSIONS['osd'].format(self._arg_parser.rgw_pool_prefix)],
"format": "json"}
ret_val, json_out, err_msg = self._common_cmd_json_gen(cmd_json)
# if there is an unsuccessful attempt,
@@ -385,7 +417,7 @@ class RadosJSON:
for pool in pools_to_validate:
if not self.cluster.pool_exists(pool):
raise ExecutionFailureException(
"The provided pool {} does not exist".format(pool))
"The provided pool, '{}', does not exist".format(pool))
self._excluded_keys.add('CLUSTER_NAME')
self.get_cephfs_data_pool_details()
self.out_map['NAMESPACE'] = self._arg_parser.namespace
@@ -528,9 +560,59 @@ class RadosJSON:
})
return json.dumps(json_out)+LINESEP
def upgrade_user_permissions(self):
# check whether the given user exists or not
cmd_json = {"prefix": "auth get", "entity": self.run_as_user, "format": "json"}
ret_val, json_out, err_msg = self._common_cmd_json_gen(cmd_json)
if ret_val != 0 or len(json_out) == 0:
raise ExecutionFailureException("'auth get {}' command failed.\n".format(self.run_as_user) +
"Error: {}".format(err_msg if ret_val != 0 else self.EMPTY_OUTPUT_LIST))
j_first = json_out[0]
existing_caps = j_first['caps']
osd_cap = "osd"
cap_keys = ["mon", "mgr", "osd"]
for eachCap in cap_keys:
min_cap_values = self.MIN_USER_CAP_PERMISSIONS.get(eachCap, '')
cur_cap_values = existing_caps.get(eachCap, '')
# detect rgw-pool-prefix
if eachCap == osd_cap:
# if directly provided through '--rgw-pool-prefix' argument, use it
if self._arg_parser.rgw_pool_prefix:
min_cap_values = min_cap_values.format(self._arg_parser.rgw_pool_prefix)
# or else try to detect one from the existing/current osd cap values
else:
rc = re.compile(r' pool=([^.]+)\.rgw\.[^ ]*')
# 'findall()' method will give a list of prefixes
# and 'set' will eliminate any duplicates
cur_rgw_pool_prefix_list = list(set(rc.findall(cur_cap_values)))
if len(cur_rgw_pool_prefix_list) != 1:
raise ExecutionFailureException("Unable to determine 'rgw-pool-prefx'. Please provide one with '--rgw-pool-prefix' flag")
min_cap_values = min_cap_values.format(cur_rgw_pool_prefix_list[0])
cur_cap_perm_list = [x.strip() for x in cur_cap_values.split(',') if x.strip()]
min_cap_perm_list = [x.strip() for x in min_cap_values.split(',') if x.strip()]
min_cap_perm_list.extend(cur_cap_perm_list)
# eliminate duplicates without using 'set'
# set re-orders items in the list and we have to keep the order
new_cap_perm_list = []
[new_cap_perm_list.append(x) for x in min_cap_perm_list if x not in new_cap_perm_list]
existing_caps[eachCap] = ", ".join(new_cap_perm_list)
cmd_json = {"prefix": "auth caps",
"entity": self.run_as_user,
"caps": ["mon", existing_caps["mon"],
"mgr", existing_caps["mgr"],
"osd", existing_caps["osd"]],
"format": "json"}
ret_val, json_out, err_msg = self._common_cmd_json_gen(cmd_json)
if ret_val != 0:
raise ExecutionFailureException("'auth caps {}' command failed.\n".format(self.run_as_user) +
"Error: {}".format(err_msg))
print("Updated user, {}, successfully.".format(self.run_as_user))
def main(self):
generated_output = ''
if self._arg_parser.format == 'json':
if self._arg_parser.upgrade:
self.upgrade_user_permissions()
elif self._arg_parser.format == 'json':
generated_output = self.gen_json_out()
elif self._arg_parser.format == 'bash':
generated_output = self.gen_shell_out()