Files
pvc/client-common/ceph.py
Joshua M. Boniface 6ee3c91a63 Use None instead of all in ceph.py
Make it like other optional args (like direction in net ACLs) and use
None instead of 'all' when specifying any option
2019-07-05 13:59:29 -04:00

1253 lines
47 KiB
Python

# ceph.py - PVC client function library, Ceph cluster fuctions
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
###############################################################################
import re
import click
import json
import time
import math
import client_lib.ansiprint as ansiprint
import client_lib.zkhandler as zkhandler
import client_lib.common as common
#
# Supplemental functions
#
# Verify OSD is valid in cluster
def verifyOSD(zk_conn, osd_id):
if zkhandler.exists(zk_conn, '/ceph/osds/{}'.format(osd_id)):
return True
else:
return False
# Verify Pool is valid in cluster
def verifyPool(zk_conn, name):
if zkhandler.exists(zk_conn, '/ceph/pools/{}'.format(name)):
return True
else:
return False
# Verify Volume is valid in cluster
def verifyVolume(zk_conn, pool, name):
if zkhandler.exists(zk_conn, '/ceph/volumes/{}/{}'.format(pool, name)):
return True
else:
return False
# Verify Snapshot is valid in cluster
def verifySnapshot(zk_conn, pool, volume, name):
if zkhandler.exists(zk_conn, '/ceph/snapshots/{}/{}/{}'.format(pool, volume, name)):
return True
else:
return False
# Verify OSD path is valid in cluster
def verifyOSDBlock(zk_conn, node, device):
for osd in zkhandler.listchildren(zk_conn, '/ceph/osds'):
osd_node = zkhandler.readdata(zk_conn, '/ceph/osds/{}/node'.format(osd))
osd_device = zkhandler.readdata(zk_conn, '/ceph/osds/{}/device'.format(osd))
if node == osd_node and device == osd_device:
return osd
return None
# Format byte sizes to/from human-readable units
unit_matrix = {
'B': 1,
'K': 1024,
'M': 1024*1024,
'G': 1024*1024*1024,
'T': 1024*1024*1024*1024,
'P': 1024*1024*1024*1024*1024
}
def format_bytes_tohuman(databytes):
datahuman = ''
for unit in sorted(unit_matrix, key=unit_matrix.get, reverse=True):
new_bytes = int(math.ceil(databytes / unit_matrix[unit]))
# Round up if 5 or more digits
if new_bytes > 9999:
# We can jump down another level
continue
else:
# We're at the end, display with this size
datahuman = '{}{}'.format(new_bytes, unit)
return datahuman
def format_bytes_fromhuman(datahuman):
# Trim off human-readable character
dataunit = datahuman[-1]
datasize = int(datahuman[:-1])
databytes = datasize * unit_matrix[dataunit]
return '{}B'.format(databytes)
#
# Status functions
#
def get_status(zk_conn):
primary_node = zkhandler.readdata(zk_conn, '/primary_node')
ceph_status = zkhandler.readdata(zk_conn, '/ceph').rstrip()
# Create a data structure for the information
status_data = {
'primary_node': primary_node,
'ceph_status': ceph_status
}
return True, status_data
def format_status(status_data):
click.echo('{bold}Ceph cluster status (primary node {end}{blue}{primary}{end}{bold}){end}\n'.format(bold=ansiprint.bold(), end=ansiprint.end(), blue=ansiprint.blue(), primary=status_data['primary_node']))
click.echo(status_data['ceph_status'])
click.echo('')
#
# OSD functions
#
def getClusterOSDList(zk_conn):
# Get a list of VNIs by listing the children of /networks
osd_list = zkhandler.listchildren(zk_conn, '/ceph/osds')
return osd_list
def getOSDInformation(zk_conn, osd_id):
# Parse the stats data
osd_stats_raw = zkhandler.readdata(zk_conn, '/ceph/osds/{}/stats'.format(osd_id))
osd_stats = dict(json.loads(osd_stats_raw))
# Deal with the size
databytes = osd_stats['kb'] * 1024
databytes_formatted = format_bytes_tohuman(databytes)
osd_stats['size'] = databytes_formatted
osd_information = {
'id': osd_id,
'stats': osd_stats
}
return osd_information
def getOutputColoursOSD(osd_information):
# Set the UP status
if osd_information['stats']['up'] == 1:
osd_up_flag = 'Yes'
osd_up_colour = ansiprint.green()
else:
osd_up_flag = 'No'
osd_up_colour = ansiprint.red()
# Set the IN status
if osd_information['stats']['in'] == 1:
osd_in = 'Yes'
osd_in_colour = ansiprint.green()
else:
osd_in = 'No'
osd_in_colour = ansiprint.red()
return osd_up_flag, osd_up_colour, osd_in_flag, osd_in_colour
def add_osd(zk_conn, node, device, weight):
# Verify the target node exists
if not common.verifyNode(zk_conn, node):
return False, 'ERROR: No node named "{}" is present in the cluster.'.format(node)
# Verify target block device isn't in use
block_osd = verifyOSDBlock(zk_conn, node, device)
if block_osd:
return False, 'ERROR: Block device {} on node {} is used by OSD {}'.format(device, node, block_osd)
# Tell the cluster to create a new OSD for the host
add_osd_string = 'osd_add {},{},{}'.format(node, device, weight)
zkhandler.writedata(zk_conn, {'/ceph/cmd': add_osd_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-osd_add':
message = 'Created new OSD with block device {} on node {}.'.format(device, node)
success = True
else:
message = 'ERROR: Failed to create new OSD; check node logs for details.'
success = False
except:
message = 'ERROR: Command ignored by node.'
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(5)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def remove_osd(zk_conn, osd_id):
if not verifyOSD(zk_conn, osd_id):
return False, 'ERROR: No OSD with ID "{}" is present in the cluster.'.format(osd_id)
# Tell the cluster to remove an OSD
remove_osd_string = 'osd_remove {}'.format(osd_id)
zkhandler.writedata(zk_conn, {'/ceph/cmd': remove_osd_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-osd_remove':
message = 'Removed OSD {} from the cluster.'.format(osd_id)
success = True
else:
message = 'ERROR: Failed to remove OSD; check node logs for details.'
success = False
except:
success = False
message = 'ERROR Command ignored by node.'
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(5)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def in_osd(zk_conn, osd_id):
if not verifyOSD(zk_conn, osd_id):
return False, 'ERROR: No OSD with ID "{}" is present in the cluster.'.format(osd_id)
# Tell the cluster to online an OSD
in_osd_string = 'osd_in {}'.format(osd_id)
zkhandler.writedata(zk_conn, {'/ceph/cmd': in_osd_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-osd_in':
message = 'Set OSD {} online in the cluster.'.format(osd_id)
success = True
else:
message = 'ERROR: Failed to set OSD online; check node logs for details.'
success = False
except:
success = False
message = 'ERROR Command ignored by node.'
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(1)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def out_osd(zk_conn, osd_id):
if not verifyOSD(zk_conn, osd_id):
return False, 'ERROR: No OSD with ID "{}" is present in the cluster.'.format(osd_id)
# Tell the cluster to offline an OSD
out_osd_string = 'osd_out {}'.format(osd_id)
zkhandler.writedata(zk_conn, {'/ceph/cmd': out_osd_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-osd_out':
message = 'Set OSD {} offline in the cluster.'.format(osd_id)
success = True
else:
message = 'ERROR: Failed to set OSD offline; check node logs for details.'
success = False
except:
success = False
message = 'ERROR Command ignored by node.'
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(1)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def set_osd(zk_conn, option):
# Tell the cluster to set an OSD property
set_osd_string = 'osd_set {}'.format(option)
zkhandler.writedata(zk_conn, {'/ceph/cmd': set_osd_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-osd_set':
message = 'Set OSD property {} on the cluster.'.format(option)
success = True
else:
message = 'ERROR: Failed to set OSD property; check node logs for details.'
success = False
except:
success = False
message = 'ERROR Command ignored by node.'
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def unset_osd(zk_conn, option):
# Tell the cluster to unset an OSD property
unset_osd_string = 'osd_unset {}'.format(option)
zkhandler.writedata(zk_conn, {'/ceph/cmd': unset_osd_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-osd_unset':
message = 'Unset OSD property {} on the cluster.'.format(option)
success = True
else:
message = 'ERROR: Failed to unset OSD property; check node logs for details.'
success = False
except:
success = False
message = 'ERROR Command ignored by node.'
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(1)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def get_list_osd(zk_conn, limit):
osd_list = []
full_osd_list = zkhandler.listchildren(zk_conn, '/ceph/osds')
for osd in full_osd_list:
if limit:
try:
# Implicitly assume fuzzy limits
if not re.match('\^.*', limit):
limit = '.*' + limit
if not re.match('.*\$', limit):
limit = limit + '.*'
if re.match(limit, osd):
osd_list.append(getOSDInformation(zk_conn, osd))
except Exception as e:
return False, 'Regex Error: {}'.format(e)
else:
osd_list.append(getOSDInformation(zk_conn, osd))
return True, osd_list
def format_list_osd(osd_list):
osd_list_output = []
osd_id_length = 3
osd_up_length = 4
osd_in_length = 4
osd_size_length = 5
osd_weight_length = 3
osd_reweight_length = 5
osd_pgs_length = 4
osd_node_length = 5
osd_used_length = 5
osd_free_length = 6
osd_util_length = 6
osd_var_length = 6
osd_wrops_length = 4
osd_wrdata_length = 5
osd_rdops_length = 4
osd_rddata_length = 5
for osd_information in osd_list:
try:
# If this happens, the node hasn't checked in fully yet, so just ignore it
if osd_information['stats']['node'] == '|':
continue
except KeyError:
continue
# Set the OSD ID length
_osd_id_length = len(osd_information['id']) + 1
if _osd_id_length > osd_id_length:
osd_id_length = _osd_id_length
_osd_node_length = len(osd_information['stats']['node']) + 1
if _osd_node_length > osd_node_length:
osd_node_length = _osd_node_length
# Set the size and length
_osd_size_length = len(str(osd_information['stats']['size'])) + 1
if _osd_size_length > osd_size_length:
osd_size_length = _osd_size_length
# Set the weight and length
_osd_weight_length = len(str(osd_information['stats']['weight'])) + 1
if _osd_weight_length > osd_weight_length:
osd_weight_length = _osd_weight_length
# Set the reweight and length
_osd_reweight_length = len(str(osd_information['stats']['reweight'])) + 1
if _osd_reweight_length > osd_reweight_length:
osd_reweight_length = _osd_reweight_length
# Set the pgs and length
_osd_pgs_length = len(str(osd_information['stats']['pgs'])) + 1
if _osd_pgs_length > osd_pgs_length:
osd_pgs_length = _osd_pgs_length
# Set the used/available/utlization%/variance and lengths
_osd_used_length = len(osd_information['stats']['used']) + 1
if _osd_used_length > osd_used_length:
osd_used_length = _osd_used_length
_osd_free_length = len(osd_information['stats']['avail']) + 1
if _osd_free_length > osd_free_length:
osd_free_length = _osd_free_length
osd_util = round(osd_information['stats']['utilization'], 2)
_osd_util_length = len(str(osd_util)) + 1
if _osd_util_length > osd_util_length:
osd_util_length = _osd_util_length
osd_var = round(osd_information['stats']['var'], 2)
_osd_var_length = len(str(osd_var)) + 1
if _osd_var_length > osd_var_length:
osd_var_length = _osd_var_length
# Set the read/write IOPS/data and length
_osd_wrops_length = len(osd_information['stats']['wr_ops']) + 1
if _osd_wrops_length > osd_wrops_length:
osd_wrops_length = _osd_wrops_length
_osd_wrdata_length = len(osd_information['stats']['wr_data']) + 1
if _osd_wrdata_length > osd_wrdata_length:
osd_wrdata_length = _osd_wrdata_length
_osd_rdops_length = len(osd_information['stats']['rd_ops']) + 1
if _osd_rdops_length > osd_rdops_length:
osd_rdops_length = _osd_rdops_length
_osd_rddata_length = len(osd_information['stats']['rd_data']) + 1
if _osd_rddata_length > osd_rddata_length:
osd_rddata_length = _osd_rddata_length
# Format the output header
osd_list_output.append('{bold}\
{osd_id: <{osd_id_length}} \
{osd_node: <{osd_node_length}} \
{osd_up: <{osd_up_length}} \
{osd_in: <{osd_in_length}} \
{osd_size: <{osd_size_length}} \
{osd_pgs: <{osd_pgs_length}} \
{osd_weight: <{osd_weight_length}} \
{osd_reweight: <{osd_reweight_length}} \
Sp: {osd_used: <{osd_used_length}} \
{osd_free: <{osd_free_length}} \
{osd_util: <{osd_util_length}} \
{osd_var: <{osd_var_length}} \
Rd: {osd_rdops: <{osd_rdops_length}} \
{osd_rddata: <{osd_rddata_length}} \
Wr: {osd_wrops: <{osd_wrops_length}} \
{osd_wrdata: <{osd_wrdata_length}} \
{end_bold}'.format(
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
osd_id_length=osd_id_length,
osd_node_length=osd_node_length,
osd_up_length=osd_up_length,
osd_in_length=osd_in_length,
osd_size_length=osd_size_length,
osd_pgs_length=osd_pgs_length,
osd_weight_length=osd_weight_length,
osd_reweight_length=osd_reweight_length,
osd_used_length=osd_used_length,
osd_free_length=osd_free_length,
osd_util_length=osd_util_length,
osd_var_length=osd_var_length,
osd_wrops_length=osd_wrops_length,
osd_wrdata_length=osd_wrdata_length,
osd_rdops_length=osd_rdops_length,
osd_rddata_length=osd_rddata_length,
osd_id='ID',
osd_node='Node',
osd_up='Up',
osd_in='In',
osd_size='Size',
osd_pgs='PGs',
osd_weight='Wt',
osd_reweight='ReWt',
osd_used='Used',
osd_free='Free',
osd_util='Util%',
osd_var='Var',
osd_wrops='OPS',
osd_wrdata='Data',
osd_rdops='OPS',
osd_rddata='Data'
)
)
for osd_information in osd_list:
try:
# If this happens, the node hasn't checked in fully yet, so just ignore it
if osd_information['stats']['node'] == '|':
continue
except KeyError:
continue
osd_up_flag, osd_up_colour, osd_in_flag, osd_in_colour = getOutputColoursOSD(osd_information)
osd_util = round(osd_information['stats']['utilization'], 2)
osd_var = round(osd_information['stats']['var'], 2)
# Format the output header
osd_list_output.append('{bold}\
{osd_id: <{osd_id_length}} \
{osd_node: <{osd_node_length}} \
{osd_up_colour}{osd_up: <{osd_up_length}}{end_colour} \
{osd_in_colour}{osd_in: <{osd_in_length}}{end_colour} \
{osd_size: <{osd_size_length}} \
{osd_pgs: <{osd_pgs_length}} \
{osd_weight: <{osd_weight_length}} \
{osd_reweight: <{osd_reweight_length}} \
{osd_used: <{osd_used_length}} \
{osd_free: <{osd_free_length}} \
{osd_util: <{osd_util_length}} \
{osd_var: <{osd_var_length}} \
{osd_rdops: <{osd_rdops_length}} \
{osd_rddata: <{osd_rddata_length}} \
{osd_wrops: <{osd_wrops_length}} \
{osd_wrdata: <{osd_wrdata_length}} \
{end_bold}'.format(
bold='',
end_bold='',
end_colour=ansiprint.end(),
osd_id_length=osd_id_length,
osd_node_length=osd_node_length,
osd_up_length=osd_up_length,
osd_in_length=osd_in_length,
osd_size_length=osd_size_length,
osd_pgs_length=osd_pgs_length,
osd_weight_length=osd_weight_length,
osd_reweight_length=osd_reweight_length,
osd_used_length=osd_used_length,
osd_free_length=osd_free_length,
osd_util_length=osd_util_length,
osd_var_length=osd_var_length,
osd_wrops_length=osd_wrops_length,
osd_wrdata_length=osd_wrdata_length,
osd_rdops_length=osd_rdops_length,
osd_rddata_length=osd_rddata_length,
osd_id=osd_information['id'],
osd_node=osd_information['stats']['node'],
osd_up_colour=osd_up_colour,
osd_up=osd_up_flag,
osd_in_colour=osd_in_colour,
osd_in=osd_in_flag,
osd_size=osd_information['stats']['size'],
osd_pgs=osd_information['stats']['pgs'],
osd_weight=osd_information['stats']['weight'],
osd_reweight=osd_information['stats']['reweight'],
osd_used=osd_information['stats']['used'],
osd_free=osd_information['stats']['avail'],
osd_util=osd_util[osd],
osd_var=osd_var[osd],
osd_wrops=osd_information['stats']['wr_ops'],
osd_wrdata=osd_information['stats']['wr_data'],
osd_rdops=osd_information['stats']['rd_ops'],
osd_rddata=osd_information['stats']['rd_data']
)
)
click.echo('\n'.join(sorted(osd_list_output)))
#
# Pool functions
#
def getPoolInformation(zk_conn, pool):
# Parse the stats data
pool_stats_raw = zkhandler.readdata(zk_conn, '/ceph/pools/{}/stats'.format(pool))
pool_stats = dict(json.loads(pool_stats_raw))
# Deal with the size issues
for datatype in 'size_bytes', 'read_bytes', 'write_bytes':
databytes = pool_stats[datatype]
databytes_formatted = format_bytes_tohuman(databytes)
new_name = datatype.replace('bytes', 'formatted')
pool_stats[new_name] = databytes_formatted
pool_information = {
'name': pool,
'stats': pool_stats
}
return pool_information
def add_pool(zk_conn, name, pgs):
# Tell the cluster to create a new pool
add_pool_string = 'pool_add {},{}'.format(name, pgs)
zkhandler.writedata(zk_conn, {'/ceph/cmd': add_pool_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-pool_add':
message = 'Created new RBD pool {} with {} PGs.'.format(name, pgs)
success = True
else:
message = 'ERROR: Failed to create new pool; check node logs for details.'
success = False
except:
message = 'ERROR: Command ignored by node.'
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(3)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def remove_pool(zk_conn, name):
if not verifyPool(zk_conn, name):
return False, 'ERROR: No pool with name "{}" is present in the cluster.'.format(name)
# Tell the cluster to create a new pool
remove_pool_string = 'pool_remove {}'.format(name)
zkhandler.writedata(zk_conn, {'/ceph/cmd': remove_pool_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-pool_remove':
message = 'Removed RBD pool {} and all volumes.'.format(name)
success = True
else:
message = 'ERROR: Failed to remove pool; check node logs for details.'
success = False
except Exception as e:
message = 'ERROR: Command ignored by node: {}'.format(e)
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(3)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def get_list_pool(zk_conn, limit):
pool_list = []
full_pool_list = zkhandler.listchildren(zk_conn, '/ceph/pools')
for pool in full_pool_list:
if limit:
try:
# Implicitly assume fuzzy limits
if not re.match('\^.*', limit):
limit = '.*' + limit
if not re.match('.*\$', limit):
limit = limit + '.*'
if re.match(limit, pool):
pool_list.append(getPoolInformation[zk_conn, pool])
except Exception as e:
return False, 'Regex Error: {}'.format(e)
else:
pool_list.append(getPoolInformation[zk_conn, pool])
return True, pool_list
def format_list_pool(pool_list):
pool_list_output = []
pool_name_length = 5
pool_id_length = 3
pool_size_length = 5
pool_num_objects_length = 6
pool_num_clones_length = 7
pool_num_copies_length = 7
pool_num_degraded_length = 9
pool_read_ops_length = 4
pool_read_data_length = 5
pool_write_ops_length = 4
pool_write_data_length = 5
for pool_information in pool_list:
# Set the Pool name length
_pool_name_length = len(pool_information['name']) + 1
if _pool_name_length > pool_name_length:
pool_name_length = _pool_name_length
# Set the id and length
_pool_id_length = len(str(pool_information['stats']['id'])) + 1
if _pool_id_length > pool_id_length:
pool_id_length = _pool_id_length
# Set the size and length
_pool_size_length = len(str(pool_information['stats']['size_formatted'])) + 1
if _pool_size_length > pool_size_length:
pool_size_length = _pool_size_length
# Set the num_objects and length
_pool_num_objects_length = len(str(pool_information['stats']['num_objects'])) + 1
if _pool_num_objects_length > pool_num_objects_length:
pool_num_objects_length = _pool_num_objects_length
# Set the num_clones and length
_pool_num_clones_length = len(str(pool_information['stats']['num_object_clones'])) + 1
if _pool_num_clones_length > pool_num_clones_length:
pool_num_clones_length = _pool_num_clones_length
# Set the num_copies and length
_pool_num_copies_length = len(str(pool_information['stats']['num_object_copies'])) + 1
if _pool_num_copies_length > pool_num_copies_length:
pool_num_copies_length = _pool_num_copies_length
# Set the num_degraded and length
_pool_num_degraded_length = len(str(pool_information['stats']['num_objects_degraded'])) + 1
if _pool_num_degraded_length > pool_num_degraded_length:
pool_num_degraded_length = _pool_num_degraded_length
# Set the read/write IOPS/data and length
_pool_write_ops_length = len(str(pool_information['stats']['write_ops'])) + 1
if _pool_write_ops_length > pool_write_ops_length:
pool_write_ops_length = _pool_write_ops_length
_pool_write_data_length = len(pool_information['stats']['write_formatted']) + 1
if _pool_write_data_length > pool_write_data_length:
pool_write_data_length = _pool_write_data_length
_pool_read_ops_length = len(str(pool_information['stats']['read_ops'])) + 1
if _pool_read_ops_length > pool_read_ops_length:
pool_read_ops_length = _pool_read_ops_length
_pool_read_data_length = len(pool_information['stats']['read_formatted']) + 1
if _pool_read_data_length > pool_read_data_length:
pool_read_data_length = _pool_read_data_length
# Format the output header
pool_list_output.append('{bold}\
{pool_id: <{pool_id_length}} \
{pool_name: <{pool_name_length}} \
{pool_size: <{pool_size_length}} \
Obj: {pool_objects: <{pool_objects_length}} \
{pool_clones: <{pool_clones_length}} \
{pool_copies: <{pool_copies_length}} \
{pool_degraded: <{pool_degraded_length}} \
Rd: {pool_read_ops: <{pool_read_ops_length}} \
{pool_read_data: <{pool_read_data_length}} \
Wr: {pool_write_ops: <{pool_write_ops_length}} \
{pool_write_data: <{pool_write_data_length}} \
{end_bold}'.format(
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
pool_id_length=pool_id_length,
pool_name_length=pool_name_length,
pool_size_length=pool_size_length,
pool_objects_length=pool_num_objects_length,
pool_clones_length=pool_num_clones_length,
pool_copies_length=pool_num_copies_length,
pool_degraded_length=pool_num_degraded_length,
pool_write_ops_length=pool_write_ops_length,
pool_write_data_length=pool_write_data_length,
pool_read_ops_length=pool_read_ops_length,
pool_read_data_length=pool_read_data_length,
pool_id='ID',
pool_name='Name',
pool_size='Used',
pool_objects='Count',
pool_clones='Clones',
pool_copies='Copies',
pool_degraded='Degraded',
pool_write_ops='OPS',
pool_write_data='Data',
pool_read_ops='OPS',
pool_read_data='Data'
)
)
for pool in pool_list:
# Format the output header
pool_list_output.append('{bold}\
{pool_id: <{pool_id_length}} \
{pool_name: <{pool_name_length}} \
{pool_size: <{pool_size_length}} \
{pool_objects: <{pool_objects_length}} \
{pool_clones: <{pool_clones_length}} \
{pool_copies: <{pool_copies_length}} \
{pool_degraded: <{pool_degraded_length}} \
{pool_read_ops: <{pool_read_ops_length}} \
{pool_read_data: <{pool_read_data_length}} \
{pool_write_ops: <{pool_write_ops_length}} \
{pool_write_data: <{pool_write_data_length}} \
{end_bold}'.format(
bold='',
end_bold='',
pool_id_length=pool_id_length,
pool_name_length=pool_name_length,
pool_size_length=pool_size_length,
pool_objects_length=pool_num_objects_length,
pool_clones_length=pool_num_clones_length,
pool_copies_length=pool_num_copies_length,
pool_degraded_length=pool_num_degraded_length,
pool_write_ops_length=pool_write_ops_length,
pool_write_data_length=pool_write_data_length,
pool_read_ops_length=pool_read_ops_length,
pool_read_data_length=pool_read_data_length,
pool_id=pool_information['stats']['id'],
pool_name=pool_information['name'],
pool_size=pool_information['stats']['size_formatted'],
pool_objects=pool_information['stats']['num_objects'],
pool_clones=pool_information['stats']['num_objects_clones'],
pool_copies=pool_information['stats']['num_objects_copies'],
pool_degraded=pool_information['stats']['num_objects_degraded'],
pool_write_ops=pool_information['stats']['write_ops'],
pool_write_data=pool_information['stats']['write_formatted'],
pool_read_ops=pool_information['stats']['read_ops'],
pool_read_data=pool_information['stats']['read_formatted']
)
)
click.echo('\n'.join(sorted(pool_list_output)))
#
# Volume functions
#
def getCephVolumes(zk_conn, pool):
volume_list = list()
if not pool:
pool_list = zkhandler.listchildren(zk_conn, '/ceph/pools')
else:
pool_list = [ pool ]
for pool_name in pool_list:
for volume_name in zkhandler.listchildren(zk_conn, '/ceph/volumes/{}'.format(pool_name)):
volume_list.append('{}/{}'.format(pool_name, volume_name))
return volume_list
def getVolumeInformation(zk_conn, pool, volume):
# Parse the stats data
volume_stats_raw = zkhandler.readdata(zk_conn, '/ceph/volumes/{}/{}/stats'.format(pool, volume))
volume_stats = dict(json.loads(volume_stats_raw))
# Format the size to something nicer
volume_stats['size'] = format_bytes_tohuman(volume_stats['size'])
volume_information = {
'name': volume,
'pool': pool,
'stats': volume_stats
}
return volume_information
def add_volume(zk_conn, pool, name, size):
# Tell the cluster to create a new volume
databytes = format_bytes_fromhuman(size)
add_volume_string = 'volume_add {},{},{}'.format(pool, name, databytes)
zkhandler.writedata(zk_conn, {'/ceph/cmd': add_volume_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-volume_add':
message = 'Created new RBD volume {} of size {} on pool {}.'.format(name, size, pool)
success = True
else:
message = 'ERROR: Failed to create new volume; check node logs for details.'
success = False
except:
message = 'ERROR: Command ignored by node.'
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(1)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def remove_volume(zk_conn, pool, name):
if not verifyVolume(zk_conn, pool, name):
return False, 'ERROR: No volume with name "{}" is present in pool {}.'.format(name, pool)
# Tell the cluster to create a new volume
remove_volume_string = 'volume_remove {},{}'.format(pool, name)
zkhandler.writedata(zk_conn, {'/ceph/cmd': remove_volume_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-volume_remove':
message = 'Removed RBD volume {} in pool {}.'.format(name, pool)
success = True
else:
message = 'ERROR: Failed to remove volume; check node logs for details.'
success = False
except Exception as e:
message = 'ERROR: Command ignored by node: {}'.format(e)
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(1)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def get_list_volume(zk_conn, pool, limit):
volume_list = []
if pool and not verifyPool(zk_conn, name):
return False, 'ERROR: No pool with name "{}" is present in the cluster.'.format(name)
full_volume_list = getCephVolumes(zk_conn, pool)
for volume in full_volume_list:
pool_name, volume_name = volume.split('/')
if limit:
try:
# Implicitly assume fuzzy limits
if not re.match('\^.*', limit):
limit = '.*' + limit
if not re.match('.*\$', limit):
limit = limit + '.*'
if re.match(limit, volume):
volume_list.append(getVolumeInformation(zk_conn, pool_name, volume_name))
except Exception as e:
return False, 'Regex Error: {}'.format(e)
else:
volume_list.append(getVolumeInformation(zk_conn, pool_name, volume_name))
return True, volume_list
def format_list_volume(volume_list):
volume_list_output = []
volume_name_length = 5
volume_pool_length = 5
volume_size_length = 5
volume_objects_length = 8
volume_order_length = 6
volume_format_length = 7
volume_features_length = 10
for volume_information in volume_list:
# Set the Volume name length
_volume_name_length = len(volume_information['name']) + 1
if _volume_name_length > volume_name_length:
volume_name_length = _volume_name_length
# Set the Volume pool length
_volume_pool_length = len(volume_information['pool']) + 1
if _volume_pool_length > volume_pool_length:
volume_pool_length = _volume_pool_length
# Set the size and length
_volume_size_length = len(str(volume_information['stats']['size'])) + 1
if _volume_size_length > volume_size_length:
volume_size_length = _volume_size_length
# Set the num_objects and length
_volume_objects_length = len(str(volume_information['stats']['objects'])) + 1
if _volume_objects_length > volume_objects_length:
volume_objects_length = _volume_objects_length
# Set the order and length
_volume_order_length = len(str(volume_information['stats']['order'])) + 1
if _volume_order_length > volume_order_length:
volume_order_length = _volume_order_length
# Set the format and length
_volume_format_length = len(str(volume_information['stats']['format'])) + 1
if _volume_format_length > volume_format_length:
volume_format_length = _volume_format_length
# Set the features and length
_volume_features_length = len(str(volume_information['stats']['features'])) + 1
if _volume_features_length > volume_features_length:
volume_features_length = _volume_features_length
# Format the output header
volume_list_output.append('{bold}\
{volume_name: <{volume_name_length}} \
{volume_pool: <{volume_pool_length}} \
{volume_size: <{volume_size_length}} \
{volume_objects: <{volume_objects_length}} \
{volume_order: <{volume_order_length}} \
{volume_format: <{volume_format_length}} \
{volume_features: <{volume_features_length}} \
{end_bold}'.format(
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
volume_name_length=volume_name_length,
volume_pool_length=volume_pool_length,
volume_size_length=volume_size_length,
volume_objects_length=volume_objects_length,
volume_order_length=volume_order_length,
volume_format_length=volume_format_length,
volume_features_length=volume_features_length,
volume_name='Name',
volume_pool='Pool',
volume_size='Size',
volume_objects='Objects',
volume_order='Order',
volume_format='Format',
volume_features='Features',
)
)
for volume_information in volume_list:
volume_list_output.append('{bold}\
{volume_name: <{volume_name_length}} \
{volume_pool: <{volume_pool_length}} \
{volume_size: <{volume_size_length}} \
{volume_objects: <{volume_objects_length}} \
{volume_order: <{volume_order_length}} \
{volume_format: <{volume_format_length}} \
{volume_features: <{volume_features_length}} \
{end_bold}'.format(
bold='',
end_bold='',
volume_name_length=volume_name_length,
volume_pool_length=volume_pool_length,
volume_size_length=volume_size_length,
volume_objects_length=volume_objects_length,
volume_order_length=volume_order_length,
volume_format_length=volume_format_length,
volume_features_length=volume_features_length,
volume_name=volume_information['name'],
volume_pool=volume_information['pool'],
volume_size=volume_information['stats']['size'],
volume_objects=volume_information['stats']['objects'],
volume_order=volume_information['stats']['order'],
volume_format=volume_information['stats']['format'],
volume_features=volume_information['stats']['features'],
)
)
click.echo('\n'.join(sorted(volume_list_output)))
#
# Snapshot functions
#
def getCephSnapshots(zk_conn, pool, volume):
snapshot_list = list()
volume_list = list()
volume_list = getCephVolumes(zk_conn, pool)
if volume:
for volume_entry in volume_list:
volume_pool, volume_name = volume_entry.split('/')
if volume_name == volume:
volume_list = [ '{}/{}'.format(volume_pool, volume_name) ]
for volume_entry in volume_list:
for snapshot_name in zkhandler.listchildren(zk_conn, '/ceph/snapshots/{}'.format(volume_entry)):
snapshot_list.append('{}@{}'.format(volume_entry, snapshot_name))
return snapshot_list
def add_snapshot(zk_conn, pool, volume, name):
# Tell the cluster to create a new snapshot
add_snapshot_string = 'snapshot_add {},{},{}'.format(pool, volume, name)
zkhandler.writedata(zk_conn, {'/ceph/cmd': add_snapshot_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-snapshot_add':
message = 'Created new RBD snapshot {} of volume {} on pool {}.'.format(name, volume, pool)
success = True
else:
message = 'ERROR: Failed to create new snapshot; check node logs for details.'
success = False
except:
message = 'ERROR: Command ignored by node.'
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(1)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def remove_snapshot(zk_conn, pool, volume, name):
if not verifySnapshot(zk_conn, pool, volume, name):
return False, 'ERROR: No snapshot with name "{}" is present of volume {} on pool {}.'.format(name, volume, pool)
# Tell the cluster to create a new snapshot
remove_snapshot_string = 'snapshot_remove {},{},{}'.format(pool, volume, name)
zkhandler.writedata(zk_conn, {'/ceph/cmd': remove_snapshot_string})
# Wait 1/2 second for the cluster to get the message and start working
time.sleep(0.5)
# Acquire a read lock, so we get the return exclusively
lock = zkhandler.readlock(zk_conn, '/ceph/cmd')
with lock:
try:
result = zkhandler.readdata(zk_conn, '/ceph/cmd').split()[0]
if result == 'success-snapshot_remove':
message = 'Removed RBD snapshot {} and all volumes.'.format(name)
success = True
else:
message = 'ERROR: Failed to remove snapshot; check node logs for details.'
success = False
except Exception as e:
message = 'ERROR: Command ignored by node: {}'.format(e)
success = False
# Acquire a write lock to ensure things go smoothly
lock = zkhandler.writelock(zk_conn, '/ceph/cmd')
with lock:
time.sleep(1)
zkhandler.writedata(zk_conn, {'/ceph/cmd': ''})
return success, message
def get_list_snapshot(zk_conn, pool, volume, limit):
snapshot_list = []
if pool and not verifyPool(zk_conn, pool):
return False, 'ERROR: No pool with name "{}" is present in the cluster.'.format(pool)
if volume and not verifyPool(zk_conn, volume):
return False, 'ERROR: No volume with name "{}" is present in the cluster.'.format(volume)
full_snapshot_list = getCephSnapshots(zk_conn, pool, volume)
for snapshot in full_snapshot_list:
volume, snapshot_name = snapshot.split('@')
pool_name, volume_name = volume.split('/')
if limit:
try:
# Implicitly assume fuzzy limits
if not re.match('\^.*', limit):
limit = '.*' + limit
if not re.match('.*\$', limit):
limit = limit + '.*'
if re.match(limit, snapshot):
snapshot_list.append(getVolumeInformation(zk_conn, pool_name, volume_name, snapshot_name))
except Exception as e:
return False, 'Regex Error: {}'.format(e)
else:
snapshot_list.append(getVolumeInformation(zk_conn, pool_name, volume_name, snapshot_name))
return True, snapshot_list
def format_list_snapshot(snapshot_list):
snapshot_list_output = []
snapshot_name_length = 5
snapshot_volume_length = 7
snapshot_pool_length = 5
for snapshot in snapshot_list:
volume, snapshot_name = snapshot.split('@')
snapshot_pool, snapshot_volume = volume.split('/')
# Set the Snapshot name length
_snapshot_name_length = len(snapshot_name) + 1
if _snapshot_name_length > snapshot_name_length:
snapshot_name_length = _snapshot_name_length
# Set the Snapshot volume length
_snapshot_volume_length = len(snapshot_volume) + 1
if _snapshot_volume_length > snapshot_volume_length:
snapshot_volume_length = _snapshot_volume_length
# Set the Snapshot pool length
_snapshot_pool_length = len(snapshot_pool) + 1
if _snapshot_pool_length > snapshot_pool_length:
snapshot_pool_length = _snapshot_pool_length
# Format the output header
snapshot_list_output.append('{bold}\
{snapshot_name: <{snapshot_name_length}} \
{snapshot_volume: <{snapshot_volume_length}} \
{snapshot_pool: <{snapshot_pool_length}} \
{end_bold}'.format(
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
snapshot_name_length=snapshot_name_length,
snapshot_volume_length=snapshot_volume_length,
snapshot_pool_length=snapshot_pool_length,
snapshot_name='Name',
snapshot_volume='Volume',
snapshot_pool='Pool',
)
)
for snapshot in snapshot_list:
volume, snapshot_name = snapshot.split('@')
snapshot_pool, snapshot_volume = volume.split('/')
snapshot_list_output.append('{bold}\
{snapshot_name: <{snapshot_name_length}} \
{snapshot_volume: <{snapshot_volume_length}} \
{snapshot_pool: <{snapshot_pool_length}} \
{end_bold}'.format(
bold='',
end_bold='',
snapshot_name_length=snapshot_name_length,
snapshot_volume_length=snapshot_volume_length,
snapshot_pool_length=snapshot_pool_length,
snapshot_name=snapshot_name,
snapshot_volume=snapshot_volume,
snapshot_pool=snapshot_pool,
)
)
click.echo('\n'.join(sorted(snapshot_list_output)))