2019-09-26 14:07:52 -04:00
#!/usr/bin/env python3
2018-10-27 18:11:58 -04:00
# ceph.py - PVC client function library, Ceph cluster fuctions
# Part of the Parallel Virtual Cluster (PVC) system
#
2022-10-06 11:55:27 -04:00
# Copyright (C) 2018-2022 Joshua M. Boniface <joshua@boniface.me>
2018-10-27 18:11:58 -04:00
#
# 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
2021-03-25 16:57:17 -04:00
# the Free Software Foundation, version 3.
2018-10-27 18:11:58 -04:00
#
# 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/>.
#
###############################################################################
2020-02-09 13:43:48 -05:00
import os
2018-10-27 18:11:58 -04:00
import re
2018-10-31 23:38:17 -04:00
import json
2018-10-30 22:41:44 -04:00
import time
2018-10-31 23:38:17 -04:00
import math
2018-10-27 18:11:58 -04:00
2021-07-01 17:33:13 -04:00
from concurrent . futures import ThreadPoolExecutor
2023-11-03 01:45:49 -04:00
from distutils . util import strtobool
2021-07-01 17:33:13 -04:00
2020-08-11 21:46:12 -04:00
import daemon_lib . vm as vm
2020-02-08 18:48:59 -05:00
import daemon_lib . common as common
2018-10-27 18:11:58 -04:00
2020-11-07 14:45:24 -05:00
2018-10-27 18:11:58 -04:00
#
# Supplemental functions
#
2023-09-12 16:41:02 -04:00
2018-10-31 23:38:17 -04:00
# Verify OSD is valid in cluster
2021-05-29 20:29:51 -04:00
def verifyOSD ( zkhandler , osd_id ) :
2021-11-06 03:02:43 -04:00
return zkhandler . exists ( ( " osd " , osd_id ) )
2018-10-31 23:38:17 -04:00
2020-11-07 14:45:24 -05:00
2018-11-01 19:55:13 -04:00
# Verify Pool is valid in cluster
2021-05-29 20:29:51 -04:00
def verifyPool ( zkhandler , name ) :
2021-11-06 03:02:43 -04:00
return zkhandler . exists ( ( " pool " , name ) )
2018-11-01 19:55:13 -04:00
2020-11-07 14:45:24 -05:00
2019-06-19 00:12:44 -04:00
# Verify Volume is valid in cluster
2021-05-29 20:29:51 -04:00
def verifyVolume ( zkhandler , pool , name ) :
2021-11-06 03:02:43 -04:00
return zkhandler . exists ( ( " volume " , f " { pool } / { name } " ) )
2019-06-19 00:12:44 -04:00
2020-11-07 14:45:24 -05:00
2019-06-19 00:12:44 -04:00
# Verify Snapshot is valid in cluster
2021-05-29 20:29:51 -04:00
def verifySnapshot ( zkhandler , pool , volume , name ) :
2021-11-06 03:02:43 -04:00
return zkhandler . exists ( ( " snapshot " , f " { pool } / { volume } / { name } " ) )
2019-06-19 00:12:44 -04:00
2020-11-07 14:45:24 -05:00
2018-10-31 23:38:17 -04:00
# Verify OSD path is valid in cluster
2021-05-29 20:29:51 -04:00
def verifyOSDBlock ( zkhandler , node , device ) :
2021-11-06 03:02:43 -04:00
for osd in zkhandler . children ( " base.osd " ) :
osd_node = zkhandler . read ( ( " osd.node " , osd ) )
osd_device = zkhandler . read ( ( " osd.device " , osd ) )
2018-10-31 23:38:17 -04:00
if node == osd_node and device == osd_device :
return osd
return None
2020-11-07 13:17:49 -05:00
2020-11-07 14:45:24 -05:00
# Matrix of human-to-byte values
2019-07-08 22:03:34 -04:00
byte_unit_matrix = {
2021-11-06 03:02:43 -04:00
" B " : 1 ,
" K " : 1024 ,
" M " : 1024 * 1024 ,
" G " : 1024 * 1024 * 1024 ,
" T " : 1024 * 1024 * 1024 * 1024 ,
" P " : 1024 * 1024 * 1024 * 1024 * 1024 ,
2023-04-28 12:01:11 -04:00
" E " : 1024 * 1024 * 1024 * 1024 * 1024 * 1024 ,
" Z " : 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 ,
" Y " : 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 ,
" R " : 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 ,
" Q " : 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 ,
2019-06-21 09:22:24 -04:00
}
2020-11-07 14:45:24 -05:00
# Matrix of human-to-metric values
ops_unit_matrix = {
2021-11-06 03:02:43 -04:00
" " : 1 ,
" K " : 1000 ,
" M " : 1000 * 1000 ,
" G " : 1000 * 1000 * 1000 ,
" T " : 1000 * 1000 * 1000 * 1000 ,
" P " : 1000 * 1000 * 1000 * 1000 * 1000 ,
2023-04-28 12:01:11 -04:00
" E " : 1000 * 1000 * 1000 * 1000 * 1000 * 1000 ,
" Z " : 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 ,
" Y " : 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 ,
" R " : 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 ,
" Q " : 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 ,
2020-11-07 14:45:24 -05:00
}
# Format byte sizes to/from human-readable units
2019-06-21 09:22:24 -04:00
def format_bytes_tohuman ( databytes ) :
2021-11-06 03:02:43 -04:00
datahuman = " "
2019-07-08 22:03:34 -04:00
for unit in sorted ( byte_unit_matrix , key = byte_unit_matrix . get , reverse = True ) :
new_bytes = int ( math . ceil ( databytes / byte_unit_matrix [ unit ] ) )
2019-06-21 09:22:24 -04:00
# 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
2021-11-06 03:02:43 -04:00
datahuman = " {} {} " . format ( new_bytes , unit )
2019-06-21 09:22:24 -04:00
return datahuman
2020-11-07 14:45:24 -05:00
2019-06-21 09:22:24 -04:00
def format_bytes_fromhuman ( datahuman ) :
2023-04-28 12:01:11 -04:00
if not re . search ( r " [A-Za-z]+ " , datahuman ) :
2021-11-06 03:02:43 -04:00
dataunit = " B "
2020-02-11 20:31:42 -05:00
datasize = int ( datahuman )
2023-04-28 12:01:11 -04:00
else :
dataunit = str ( re . match ( r " [0-9]+([A-Za-z])[iBb]* " , datahuman ) . group ( 1 ) )
datasize = int ( re . match ( r " ([0-9]+)[A-Za-z]+ " , datahuman ) . group ( 1 ) )
if byte_unit_matrix . get ( dataunit ) :
databytes = datasize * byte_unit_matrix [ dataunit ]
return databytes
else :
return None
2018-11-01 23:03:27 -04:00
2020-11-07 13:17:49 -05:00
2019-07-08 22:03:34 -04:00
# Format ops sizes to/from human-readable units
def format_ops_tohuman ( dataops ) :
2021-11-06 03:02:43 -04:00
datahuman = " "
2019-07-08 22:03:34 -04:00
for unit in sorted ( ops_unit_matrix , key = ops_unit_matrix . get , reverse = True ) :
new_ops = int ( math . ceil ( dataops / ops_unit_matrix [ unit ] ) )
# Round up if 5 or more digits
if new_ops > 9999 :
# We can jump down another level
continue
else :
# We're at the end, display with this size
2021-11-06 03:02:43 -04:00
datahuman = " {} {} " . format ( new_ops , unit )
2019-07-08 22:03:34 -04:00
return datahuman
2020-11-07 14:45:24 -05:00
2019-07-08 22:03:34 -04:00
def format_ops_fromhuman ( datahuman ) :
# Trim off human-readable character
dataunit = datahuman [ - 1 ]
datasize = int ( datahuman [ : - 1 ] )
dataops = datasize * ops_unit_matrix [ dataunit ]
2021-11-06 03:02:43 -04:00
return " {} " . format ( dataops )
2019-07-08 22:03:34 -04:00
2020-11-07 14:45:24 -05:00
2019-12-06 00:06:21 -05:00
def format_pct_tohuman ( datapct ) :
datahuman = " {0:.1f} " . format ( float ( datapct * 100.0 ) )
return datahuman
2020-11-07 14:45:24 -05:00
2018-10-30 09:17:32 -04:00
#
2019-07-04 23:09:16 -04:00
# Status functions
2018-10-30 09:17:32 -04:00
#
2021-05-29 20:29:51 -04:00
def get_status ( zkhandler ) :
2021-11-06 03:02:43 -04:00
primary_node = zkhandler . read ( " base.config.primary_node " )
ceph_status = zkhandler . read ( " base.storage " ) . rstrip ( )
2019-07-05 00:29:47 -04:00
# Create a data structure for the information
status_data = {
2021-11-06 03:02:43 -04:00
" type " : " status " ,
" primary_node " : primary_node ,
" ceph_data " : ceph_status ,
2019-07-08 10:56:33 -04:00
}
return True , status_data
2020-11-07 14:45:24 -05:00
2023-02-15 15:16:02 -05:00
def get_health ( zkhandler ) :
primary_node = zkhandler . read ( " base.config.primary_node " )
ceph_health = zkhandler . read ( " base.storage.health " ) . rstrip ( )
# Create a data structure for the information
status_data = {
" type " : " health " ,
" primary_node " : primary_node ,
" ceph_data " : ceph_health ,
}
return True , status_data
2021-05-29 20:29:51 -04:00
def get_util ( zkhandler ) :
2021-11-06 03:02:43 -04:00
primary_node = zkhandler . read ( " base.config.primary_node " )
ceph_df = zkhandler . read ( " base.storage.util " ) . rstrip ( )
2019-07-08 10:56:33 -04:00
# Create a data structure for the information
status_data = {
2021-11-06 03:02:43 -04:00
" type " : " utilization " ,
" primary_node " : primary_node ,
" ceph_data " : ceph_df ,
2019-07-05 00:29:47 -04:00
}
2019-07-05 00:44:40 -04:00
return True , status_data
2019-07-26 15:10:47 -04:00
2019-07-04 23:09:16 -04:00
#
# OSD functions
#
2021-05-29 20:29:51 -04:00
def getClusterOSDList ( zkhandler ) :
2018-10-30 09:17:32 -04:00
# Get a list of VNIs by listing the children of /networks
2021-11-06 03:02:43 -04:00
return zkhandler . children ( " base.osd " )
2018-10-30 09:17:32 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def getOSDInformation ( zkhandler , osd_id ) :
2021-09-23 13:59:49 -04:00
# Get the devices
2023-11-03 16:37:55 -04:00
osd_fsid = zkhandler . read ( ( " osd.fsid " , osd_id ) )
2022-04-29 13:26:36 -04:00
osd_node = zkhandler . read ( ( " osd.node " , osd_id ) )
2021-11-06 03:02:43 -04:00
osd_device = zkhandler . read ( ( " osd.device " , osd_id ) )
2023-11-03 01:45:49 -04:00
osd_is_split = bool ( strtobool ( zkhandler . read ( ( " osd.is_split " , osd_id ) ) ) )
2021-11-06 03:02:43 -04:00
osd_db_device = zkhandler . read ( ( " osd.db_device " , osd_id ) )
2018-10-30 09:17:32 -04:00
# Parse the stats data
2021-11-06 03:02:43 -04:00
osd_stats_raw = zkhandler . read ( ( " osd.stats " , osd_id ) )
2018-10-31 23:38:17 -04:00
osd_stats = dict ( json . loads ( osd_stats_raw ) )
2018-10-30 09:17:32 -04:00
2019-07-05 00:29:47 -04:00
osd_information = {
2021-11-06 03:02:43 -04:00
" id " : osd_id ,
2023-11-03 16:37:55 -04:00
" fsid " : osd_fsid ,
2022-04-29 13:26:36 -04:00
" node " : osd_node ,
2021-11-06 03:02:43 -04:00
" device " : osd_device ,
2023-11-01 21:17:38 -04:00
" is_split " : osd_is_split ,
2021-11-06 03:02:43 -04:00
" db_device " : osd_db_device ,
" stats " : osd_stats ,
2019-07-05 00:29:47 -04:00
}
return osd_information
2020-11-07 14:45:24 -05:00
2021-09-23 13:59:49 -04:00
# OSD DB VG actions use the /cmd/ceph pipe
# These actions must occur on the specific node they reference
def add_osd_db_vg ( zkhandler , node , device ) :
# Verify the target node exists
if not common . verifyNode ( zkhandler , node ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No node named " {} " is present in the cluster. ' . format (
node
)
2021-09-23 13:59:49 -04:00
# Tell the cluster to create a new OSD for the host
2021-11-06 03:02:43 -04:00
add_osd_db_vg_string = " db_vg_add {} , {} " . format ( node , device )
zkhandler . write ( [ ( " base.cmd.ceph " , add_osd_db_vg_string ) ] )
2021-09-23 13:59:49 -04:00
# 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
2021-11-06 03:02:43 -04:00
with zkhandler . readlock ( " base.cmd.ceph " ) :
2021-09-23 13:59:49 -04:00
try :
2021-11-06 03:02:43 -04:00
result = zkhandler . read ( " base.cmd.ceph " ) . split ( ) [ 0 ]
if result == " success-db_vg_add " :
message = ' Created new OSD database VG at " {} " on node " {} " . ' . format (
device , node
)
2021-09-23 13:59:49 -04:00
success = True
else :
2021-11-06 03:02:43 -04:00
message = " ERROR: Failed to create new OSD database VG; check node logs for details. "
2021-09-23 13:59:49 -04:00
success = False
except Exception :
2021-11-06 03:02:43 -04:00
message = " ERROR: Command ignored by node. "
2021-09-23 13:59:49 -04:00
success = False
# Acquire a write lock to ensure things go smoothly
2021-11-06 03:02:43 -04:00
with zkhandler . writelock ( " base.cmd.ceph " ) :
2021-09-23 13:59:49 -04:00
time . sleep ( 0.5 )
2021-11-06 03:02:43 -04:00
zkhandler . write ( [ ( " base.cmd.ceph " , " " ) ] )
2021-09-23 13:59:49 -04:00
return success , message
2022-05-06 15:31:58 -04:00
# OSD actions use the /cmd/ceph pipe
2020-02-08 23:35:30 -05:00
# These actions must occur on the specific node they reference
2023-11-01 21:17:38 -04:00
def add_osd (
zkhandler ,
node ,
device ,
weight ,
2023-11-02 13:29:47 -04:00
ext_db_ratio = None ,
ext_db_size = None ,
split_count = None ,
2023-11-01 21:17:38 -04:00
) :
2023-11-02 13:29:47 -04:00
# Verify that options are valid
if ext_db_ratio is not None and ext_db_size is not None :
return (
False ,
" ERROR: Both an ext_db_ratio and ext_db_size were specified; choose only one. " ,
)
2019-07-05 00:29:47 -04:00
# Verify the target node exists
2021-05-29 20:29:51 -04:00
if not common . verifyNode ( zkhandler , node ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No node named " {} " is present in the cluster. ' . format (
node
)
2019-07-05 00:29:47 -04:00
# Verify target block device isn't in use
2021-05-29 20:29:51 -04:00
block_osd = verifyOSDBlock ( zkhandler , node , device )
2019-07-05 00:29:47 -04:00
if block_osd :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: Block device " {} " on node " {} " is used by OSD " {} " ' . format (
device , node , block_osd
) ,
)
2019-07-05 00:29:47 -04:00
# Tell the cluster to create a new OSD for the host
2023-11-02 13:29:47 -04:00
add_osd_string = " osd_add {} , {} , {} , {} , {} , {} " . format (
node , device , weight , ext_db_ratio , ext_db_size , split_count
2021-11-06 03:02:43 -04:00
)
zkhandler . write ( [ ( " base.cmd.ceph " , add_osd_string ) ] )
2019-07-05 00:29:47 -04:00
# 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
2021-11-06 03:02:43 -04:00
with zkhandler . readlock ( " base.cmd.ceph " ) :
2019-07-05 00:29:47 -04:00
try :
2021-11-06 03:02:43 -04:00
result = zkhandler . read ( " base.cmd.ceph " ) . split ( ) [ 0 ]
if result == " success-osd_add " :
2023-11-01 21:17:38 -04:00
message = f ' Created { split_count } new OSD(s) on node " { node } " block device " { device } " '
2019-07-05 00:29:47 -04:00
success = True
else :
2023-11-01 21:17:38 -04:00
message = " ERROR: Failed to create OSD(s); check node logs for details. "
2019-07-05 00:29:47 -04:00
success = False
2020-11-06 19:24:10 -05:00
except Exception :
2021-11-06 03:02:43 -04:00
message = " ERROR: Command ignored by node. "
2019-07-05 00:29:47 -04:00
success = False
# Acquire a write lock to ensure things go smoothly
2021-11-06 03:02:43 -04:00
with zkhandler . writelock ( " base.cmd.ceph " ) :
2020-01-08 18:21:28 -05:00
time . sleep ( 0.5 )
2021-11-06 03:02:43 -04:00
zkhandler . write ( [ ( " base.cmd.ceph " , " " ) ] )
2019-07-05 00:29:47 -04:00
return success , message
2020-11-07 14:45:24 -05:00
2023-11-03 01:45:49 -04:00
def replace_osd (
zkhandler ,
osd_id ,
new_device ,
old_device = None ,
weight = None ,
ext_db_ratio = None ,
ext_db_size = None ,
) :
2022-05-06 15:31:58 -04:00
# Get current OSD information
osd_information = getOSDInformation ( zkhandler , osd_id )
node = osd_information [ " node " ]
# Verify target block device isn't in use
block_osd = verifyOSDBlock ( zkhandler , node , new_device )
if block_osd and block_osd != osd_id :
return (
False ,
' ERROR: Block device " {} " on node " {} " is used by OSD " {} " ' . format (
new_device , node , block_osd
) ,
)
# Tell the cluster to create a new OSD for the host
2023-11-03 01:45:49 -04:00
replace_osd_string = " osd_replace {} , {} , {} , {} , {} , {} , {} " . format (
node , osd_id , new_device , old_device , weight , ext_db_ratio , ext_db_size
2022-05-06 15:31:58 -04:00
)
zkhandler . write ( [ ( " base.cmd.ceph " , replace_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
with zkhandler . readlock ( " base.cmd.ceph " ) :
try :
result = zkhandler . read ( " base.cmd.ceph " ) . split ( ) [ 0 ]
if result == " success-osd_replace " :
message = ' Replaced OSD {} with block device " {} " on node " {} " . ' . format (
osd_id , new_device , node
)
success = True
else :
message = " ERROR: Failed to replace OSD; check node logs for details. "
success = False
except Exception :
message = " ERROR: Command ignored by node. "
success = False
# Acquire a write lock to ensure things go smoothly
with zkhandler . writelock ( " base.cmd.ceph " ) :
time . sleep ( 0.5 )
zkhandler . write ( [ ( " base.cmd.ceph " , " " ) ] )
return success , message
def refresh_osd ( zkhandler , osd_id , device ) :
# Get current OSD information
osd_information = getOSDInformation ( zkhandler , osd_id )
node = osd_information [ " node " ]
ext_db_flag = True if osd_information [ " db_device " ] else False
# Verify target block device isn't in use
block_osd = verifyOSDBlock ( zkhandler , node , device )
if not block_osd or block_osd != osd_id :
return (
False ,
' ERROR: Block device " {} " on node " {} " is not used by OSD " {} " ; use replace instead ' . format (
device , node , osd_id
) ,
)
# Tell the cluster to create a new OSD for the host
refresh_osd_string = " osd_refresh {} , {} , {} , {} " . format (
node , osd_id , device , ext_db_flag
)
zkhandler . write ( [ ( " base.cmd.ceph " , refresh_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
with zkhandler . readlock ( " base.cmd.ceph " ) :
try :
result = zkhandler . read ( " base.cmd.ceph " ) . split ( ) [ 0 ]
if result == " success-osd_refresh " :
message = (
' Refreshed OSD {} with block device " {} " on node " {} " . ' . format (
osd_id , device , node
)
)
success = True
else :
message = " ERROR: Failed to refresh OSD; check node logs for details. "
success = False
except Exception :
message = " ERROR: Command ignored by node. "
success = False
# Acquire a write lock to ensure things go smoothly
with zkhandler . writelock ( " base.cmd.ceph " ) :
time . sleep ( 0.5 )
zkhandler . write ( [ ( " base.cmd.ceph " , " " ) ] )
return success , message
2022-04-29 11:16:33 -04:00
def remove_osd ( zkhandler , osd_id , force_flag ) :
2021-05-29 20:29:51 -04:00
if not verifyOSD ( zkhandler , osd_id ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No OSD with ID " {} " is present in the cluster. ' . format (
osd_id
)
2019-07-05 00:29:47 -04:00
# Tell the cluster to remove an OSD
2022-04-29 13:26:36 -04:00
remove_osd_string = " osd_remove {} , {} " . format ( osd_id , str ( force_flag ) )
2021-11-06 03:02:43 -04:00
zkhandler . write ( [ ( " base.cmd.ceph " , remove_osd_string ) ] )
2019-07-05 00:29:47 -04:00
# 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
2021-11-06 03:02:43 -04:00
with zkhandler . readlock ( " base.cmd.ceph " ) :
2019-07-05 00:29:47 -04:00
try :
2021-11-06 03:02:43 -04:00
result = zkhandler . read ( " base.cmd.ceph " ) . split ( ) [ 0 ]
if result == " success-osd_remove " :
2019-07-08 22:37:26 -04:00
message = ' Removed OSD " {} " from the cluster. ' . format ( osd_id )
2019-07-05 00:29:47 -04:00
success = True
else :
2021-11-06 03:02:43 -04:00
message = " ERROR: Failed to remove OSD; check node logs for details. "
2019-07-05 00:29:47 -04:00
success = False
2020-11-06 19:24:10 -05:00
except Exception :
2019-07-05 00:29:47 -04:00
success = False
2021-11-06 03:02:43 -04:00
message = " ERROR Command ignored by node. "
2018-10-31 23:38:17 -04:00
2019-07-05 00:29:47 -04:00
# Acquire a write lock to ensure things go smoothly
2021-11-06 03:02:43 -04:00
with zkhandler . writelock ( " base.cmd.ceph " ) :
2020-01-08 18:21:28 -05:00
time . sleep ( 0.5 )
2021-11-06 03:02:43 -04:00
zkhandler . write ( [ ( " base.cmd.ceph " , " " ) ] )
2019-07-05 00:29:47 -04:00
return success , message
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def in_osd ( zkhandler , osd_id ) :
if not verifyOSD ( zkhandler , osd_id ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No OSD with ID " {} " is present in the cluster. ' . format (
osd_id
)
2019-07-05 00:29:47 -04:00
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command ( " ceph osd in {} " . format ( osd_id ) )
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return False , " ERROR: Failed to enable OSD {} : {} " . format ( osd_id , stderr )
2019-07-05 00:29:47 -04:00
2021-11-06 03:02:43 -04:00
return True , " Set OSD {} online. " . format ( osd_id )
2019-07-05 00:29:47 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def out_osd ( zkhandler , osd_id ) :
if not verifyOSD ( zkhandler , osd_id ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No OSD with ID " {} " is present in the cluster. ' . format (
osd_id
)
2019-07-05 00:29:47 -04:00
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command ( " ceph osd out {} " . format ( osd_id ) )
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return False , " ERROR: Failed to disable OSD {} : {} " . format ( osd_id , stderr )
2019-07-05 00:29:47 -04:00
2021-11-06 03:02:43 -04:00
return True , " Set OSD {} offline. " . format ( osd_id )
2019-07-05 00:29:47 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def set_osd ( zkhandler , option ) :
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command ( " ceph osd set {} " . format ( option ) )
2020-02-08 23:35:30 -05:00
if retcode :
return False , ' ERROR: Failed to set property " {} " : {} ' . format ( option , stderr )
2019-07-05 00:29:47 -04:00
2020-02-08 23:35:30 -05:00
return True , ' Set OSD property " {} " . ' . format ( option )
2019-07-05 00:29:47 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def unset_osd ( zkhandler , option ) :
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command ( " ceph osd unset {} " . format ( option ) )
2020-02-08 23:35:30 -05:00
if retcode :
return False , ' ERROR: Failed to unset property " {} " : {} ' . format ( option , stderr )
2019-07-05 00:29:47 -04:00
2020-02-08 23:35:30 -05:00
return True , ' Unset OSD property " {} " . ' . format ( option )
2019-07-05 00:29:47 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def get_list_osd ( zkhandler , limit , is_fuzzy = True ) :
2019-07-05 00:29:47 -04:00
osd_list = [ ]
2021-11-06 03:02:43 -04:00
full_osd_list = zkhandler . children ( " base.osd " )
2019-07-05 00:29:47 -04:00
2019-07-26 15:03:48 -04:00
if is_fuzzy and limit :
# Implicitly assume fuzzy limits
2021-11-06 03:02:43 -04:00
if not re . match ( r " \ ^.* " , limit ) :
limit = " .* " + limit
if not re . match ( r " .* \ $ " , limit ) :
limit = limit + " .* "
2019-07-26 15:03:48 -04:00
2019-07-05 00:29:47 -04:00
for osd in full_osd_list :
if limit :
try :
2021-12-06 16:35:29 -05:00
if re . fullmatch ( limit , osd ) :
2021-05-29 20:29:51 -04:00
osd_list . append ( getOSDInformation ( zkhandler , osd ) )
2019-07-05 00:29:47 -04:00
except Exception as e :
2021-11-06 03:02:43 -04:00
return False , " Regex Error: {} " . format ( e )
2019-07-05 00:29:47 -04:00
else :
2021-05-29 20:29:51 -04:00
osd_list . append ( getOSDInformation ( zkhandler , osd ) )
2019-07-05 00:29:47 -04:00
2021-11-06 03:02:43 -04:00
return True , sorted ( osd_list , key = lambda x : int ( x [ " id " ] ) )
2019-07-05 00:29:47 -04:00
2018-10-31 23:38:17 -04:00
2019-07-05 00:29:47 -04:00
#
# Pool functions
#
2021-05-29 20:29:51 -04:00
def getPoolInformation ( zkhandler , pool ) :
2019-07-05 00:29:47 -04:00
# Parse the stats data
2021-11-06 03:02:43 -04:00
pool_stats_raw = zkhandler . read ( ( " pool.stats " , pool ) )
2019-07-05 00:29:47 -04:00
pool_stats = dict ( json . loads ( pool_stats_raw ) )
2021-05-29 20:29:51 -04:00
volume_count = len ( getCephVolumes ( zkhandler , pool ) )
2021-12-28 20:39:50 -05:00
tier = zkhandler . read ( ( " pool.tier " , pool ) )
if tier is None :
tier = " default "
2021-12-28 21:08:04 -05:00
pgs = zkhandler . read ( ( " pool.pgs " , pool ) )
2021-12-28 20:39:50 -05:00
pool_information = {
" name " : pool ,
" volume_count " : volume_count ,
" tier " : tier ,
2021-12-28 21:08:04 -05:00
" pgs " : pgs ,
2021-12-28 20:39:50 -05:00
" stats " : pool_stats ,
}
2019-07-05 00:29:47 -04:00
return pool_information
2018-10-31 23:38:17 -04:00
2020-11-07 14:45:24 -05:00
2021-12-28 20:39:50 -05:00
def add_pool ( zkhandler , name , pgs , replcfg , tier = None ) :
2020-02-08 23:35:30 -05:00
# Prepare the copies/mincopies variables
try :
2021-11-06 03:02:43 -04:00
copies , mincopies = replcfg . split ( " , " )
copies = int ( copies . replace ( " copies= " , " " ) )
mincopies = int ( mincopies . replace ( " mincopies= " , " " ) )
2020-11-06 19:24:10 -05:00
except Exception :
2020-02-08 23:35:30 -05:00
copies = None
mincopies = None
if not copies or not mincopies :
2021-12-28 20:39:50 -05:00
return False , f ' ERROR: Replication configuration " { replcfg } " is not valid. '
# Prepare the tiers if applicable
if tier is not None and tier in [ " hdd " , " ssd " , " nvme " ] :
crush_rule = f " { tier } _tier "
# Create a CRUSH rule for the relevant tier
retcode , stdout , stderr = common . run_os_command (
f " ceph osd crush rule create-replicated { crush_rule } default host { tier } "
2021-11-06 03:02:43 -04:00
)
2021-12-28 20:39:50 -05:00
if retcode :
return (
False ,
f " ERROR: Failed to create CRUSH rule { tier } for pool { name } : { stderr } " ,
)
else :
tier = " default "
crush_rule = " replicated "
# Create the pool
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
2021-12-28 20:39:50 -05:00
f " ceph osd pool create { name } { pgs } { pgs } { crush_rule } "
2021-11-06 03:02:43 -04:00
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-12-28 20:39:50 -05:00
return False , f ' ERROR: Failed to create pool " { name } " with { pgs } PGs: { stderr } '
2020-11-06 19:05:48 -05:00
2021-12-28 20:39:50 -05:00
# Set the size and minsize
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
2021-12-28 20:39:50 -05:00
f " ceph osd pool set { name } size { copies } "
2021-11-06 03:02:43 -04:00
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-12-28 20:39:50 -05:00
return False , f ' ERROR: Failed to set pool " { name } " size of { copies } : { stderr } '
2020-02-08 23:35:30 -05:00
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
2021-12-28 20:39:50 -05:00
f " ceph osd pool set { name } min_size { mincopies } "
2021-11-06 03:02:43 -04:00
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-12-28 20:39:50 -05:00
return (
False ,
f ' ERROR: Failed to set pool " { name } " minimum size of { mincopies } : { stderr } ' ,
2021-11-06 03:02:43 -04:00
)
2020-02-08 23:35:30 -05:00
2021-12-28 20:39:50 -05:00
# Enable RBD application
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
2021-12-28 20:39:50 -05:00
f " ceph osd pool application enable { name } rbd "
2021-11-06 03:02:43 -04:00
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return (
False ,
2021-12-28 20:39:50 -05:00
f ' ERROR: Failed to enable RBD application on pool " { name } " : { stderr } ' ,
2021-11-06 03:02:43 -04:00
)
2020-02-08 23:35:30 -05:00
2021-12-28 20:39:50 -05:00
# Add the new pool to Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . write (
[
( ( " pool " , name ) , " " ) ,
( ( " pool.pgs " , name ) , pgs ) ,
2021-12-28 20:39:50 -05:00
( ( " pool.tier " , name ) , tier ) ,
2021-11-06 03:02:43 -04:00
( ( " pool.stats " , name ) , " {} " ) ,
( ( " volume " , name ) , " " ) ,
( ( " snapshot " , name ) , " " ) ,
]
)
2020-02-08 23:35:30 -05:00
2021-12-28 20:39:50 -05:00
return True , f ' Created RBD pool " { name } " with { pgs } PGs '
2019-07-04 23:09:16 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def remove_pool ( zkhandler , name ) :
if not verifyPool ( zkhandler , name ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No pool with name " {} " is present in the cluster. ' . format (
name
)
2019-07-04 23:09:16 -04:00
2020-02-08 23:35:30 -05:00
# 1. Remove pool volumes
2021-11-06 03:02:43 -04:00
for volume in zkhandler . children ( ( " volume " , name ) ) :
2021-05-29 20:29:51 -04:00
remove_volume ( zkhandler , name , volume )
2019-07-04 23:09:16 -04:00
2020-02-08 23:35:30 -05:00
# 2. Remove the pool
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" ceph osd pool rm {pool} {pool} --yes-i-really-really-mean-it " . format ( pool = name )
)
2020-02-08 23:35:30 -05:00
if retcode :
return False , ' ERROR: Failed to remove pool " {} " : {} ' . format ( name , stderr )
2019-07-04 23:09:16 -04:00
2020-02-08 23:35:30 -05:00
# 3. Delete pool from Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . delete (
[
( " pool " , name ) ,
( " volume " , name ) ,
( " snapshot " , name ) ,
]
)
2020-02-08 23:35:30 -05:00
return True , ' Removed RBD pool " {} " and all volumes. ' . format ( name )
2019-07-04 23:09:16 -04:00
2020-11-07 14:45:24 -05:00
2021-12-28 21:41:41 -05:00
def set_pgs_pool ( zkhandler , name , pgs ) :
if not verifyPool ( zkhandler , name ) :
return False , f ' ERROR: No pool with name " { name } " is present in the cluster. '
# Validate new PGs count
pgs = int ( pgs )
if ( pgs == 0 ) or ( pgs & ( pgs - 1 ) != 0 ) :
return (
False ,
f ' ERROR: Invalid PGs number " { pgs } " : must be a non-zero power of 2. ' ,
)
# Set the new pgs number
retcode , stdout , stderr = common . run_os_command (
f " ceph osd pool set { name } pg_num { pgs } "
)
if retcode :
return False , f " ERROR: Failed to set pg_num on pool { name } to { pgs } : { stderr } "
# Set the new pgps number if increasing
current_pgs = int ( zkhandler . read ( ( " pool.pgs " , name ) ) )
if current_pgs > = pgs :
retcode , stdout , stderr = common . run_os_command (
f " ceph osd pool set { name } pgp_num { pgs } "
)
if retcode :
return (
False ,
f " ERROR: Failed to set pg_num on pool { name } to { pgs } : { stderr } " ,
)
# Update Zookeeper count
zkhandler . write (
[
( ( " pool.pgs " , name ) , pgs ) ,
]
)
return True , f ' Set PGs count to { pgs } for RBD pool " { name } " . '
2021-05-29 20:29:51 -04:00
def get_list_pool ( zkhandler , limit , is_fuzzy = True ) :
2021-11-06 03:02:43 -04:00
full_pool_list = zkhandler . children ( " base.pool " )
2019-07-04 23:09:16 -04:00
2021-12-06 16:35:29 -05:00
if is_fuzzy and limit :
# Implicitly assume fuzzy limits
if not re . match ( r " \ ^.* " , limit ) :
limit = " .* " + limit
if not re . match ( r " .* \ $ " , limit ) :
limit = limit + " .* "
2020-11-06 19:05:48 -05:00
2021-07-01 17:33:13 -04:00
get_pool_info = dict ( )
2019-07-05 00:29:47 -04:00
for pool in full_pool_list :
2021-07-01 17:33:13 -04:00
is_limit_match = False
2019-07-05 00:29:47 -04:00
if limit :
try :
2021-12-06 16:35:29 -05:00
if re . fullmatch ( limit , pool ) :
2021-07-01 17:33:13 -04:00
is_limit_match = True
2019-07-05 00:29:47 -04:00
except Exception as e :
2021-11-06 03:02:43 -04:00
return False , " Regex Error: {} " . format ( e )
2019-07-05 00:29:47 -04:00
else :
2021-07-01 17:33:13 -04:00
is_limit_match = True
get_pool_info [ pool ] = True if is_limit_match else False
pool_execute_list = [ pool for pool in full_pool_list if get_pool_info [ pool ] ]
pool_data_list = list ( )
2021-11-06 03:02:43 -04:00
with ThreadPoolExecutor ( max_workers = 32 , thread_name_prefix = " pool_list " ) as executor :
2021-07-01 17:33:13 -04:00
futures = [ ]
for pool in pool_execute_list :
futures . append ( executor . submit ( getPoolInformation , zkhandler , pool ) )
for future in futures :
pool_data_list . append ( future . result ( ) )
2019-07-04 23:09:16 -04:00
2021-12-28 21:03:10 -05:00
return True , sorted ( pool_data_list , key = lambda x : int ( x [ " stats " ] . get ( " id " , 0 ) ) )
2019-07-04 23:09:16 -04:00
2018-10-31 23:38:17 -04:00
2019-07-05 00:29:47 -04:00
#
# Volume functions
#
2021-05-29 20:29:51 -04:00
def getCephVolumes ( zkhandler , pool ) :
2019-07-05 00:29:47 -04:00
volume_list = list ( )
2019-07-05 13:57:15 -04:00
if not pool :
2021-11-06 03:02:43 -04:00
pool_list = zkhandler . children ( " base.pool " )
2019-07-05 00:29:47 -04:00
else :
2020-11-07 13:02:54 -05:00
pool_list = [ pool ]
2018-10-31 23:38:17 -04:00
2019-07-05 00:29:47 -04:00
for pool_name in pool_list :
2021-11-06 03:02:43 -04:00
for volume_name in zkhandler . children ( ( " volume " , pool_name ) ) :
volume_list . append ( " {} / {} " . format ( pool_name , volume_name ) )
2019-06-21 09:05:00 -04:00
2019-07-05 00:29:47 -04:00
return volume_list
2018-10-28 22:15:25 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def getVolumeInformation ( zkhandler , pool , volume ) :
2019-07-05 00:29:47 -04:00
# Parse the stats data
2021-11-06 03:02:43 -04:00
volume_stats_raw = zkhandler . read ( ( " volume.stats " , f " { pool } / { volume } " ) )
2019-07-05 00:29:47 -04:00
volume_stats = dict ( json . loads ( volume_stats_raw ) )
# Format the size to something nicer
2021-11-06 03:02:43 -04:00
volume_stats [ " size " ] = format_bytes_tohuman ( volume_stats [ " size " ] )
2018-10-30 22:41:44 -04:00
2021-11-06 03:02:43 -04:00
volume_information = { " name " : volume , " pool " : pool , " stats " : volume_stats }
2019-07-05 00:29:47 -04:00
return volume_information
2019-07-04 23:09:16 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def add_volume ( zkhandler , pool , name , size ) :
2021-02-17 11:27:26 -05:00
# 1. Verify the size of the volume
2021-05-29 20:29:51 -04:00
pool_information = getPoolInformation ( zkhandler , pool )
2021-02-17 11:27:26 -05:00
size_bytes = format_bytes_fromhuman ( size )
2023-04-28 12:01:11 -04:00
if size_bytes is None :
return (
False ,
f " ERROR: Requested volume size ' { size } ' does not have a valid SI unit " ,
)
2021-11-06 03:02:43 -04:00
if size_bytes > = int ( pool_information [ " stats " ] [ " free_bytes " ] ) :
return (
False ,
2023-04-28 12:01:11 -04:00
f " ERROR: Requested volume size ' { format_bytes_tohuman ( size_bytes ) } ' is greater than the available free space in the pool ( ' { format_bytes_tohuman ( pool_information [ ' stats ' ] [ ' free_bytes ' ] ) } ' ) " ,
2021-11-06 03:02:43 -04:00
)
2021-07-29 15:30:00 -04:00
2021-02-17 11:27:26 -05:00
# 2. Create the volume
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
2023-09-30 12:37:43 -04:00
" rbd create --size {} B {} / {} " . format ( size_bytes , pool , name )
2021-11-06 03:02:43 -04:00
)
2020-02-08 23:35:30 -05:00
if retcode :
return False , ' ERROR: Failed to create RBD volume " {} " : {} ' . format ( name , stderr )
# 2. Get volume stats
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd info --format json {} / {} " . format ( pool , name )
)
2020-02-08 23:35:30 -05:00
volstats = stdout
# 3. Add the new volume to Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . write (
[
( ( " volume " , f " { pool } / { name } " ) , " " ) ,
( ( " volume.stats " , f " { pool } / { name } " ) , volstats ) ,
( ( " snapshot " , f " { pool } / { name } " ) , " " ) ,
]
)
2020-02-08 23:35:30 -05:00
2023-04-28 12:01:11 -04:00
return True , ' Created RBD volume " {} " of size " {} " in pool " {} " . ' . format (
name , format_bytes_tohuman ( size_bytes ) , pool
)
2020-02-08 23:35:30 -05:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def clone_volume ( zkhandler , pool , name_src , name_new ) :
if not verifyVolume ( zkhandler , pool , name_src ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
name_src , pool
)
2020-02-08 23:35:30 -05:00
# 1. Clone the volume
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd copy {} / {} {} / {} " . format ( pool , name_src , pool , name_new )
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: Failed to clone RBD volume " {} " to " {} " in pool " {} " : {} ' . format (
name_src , name_new , pool , stderr
) ,
)
2020-02-08 23:35:30 -05:00
# 2. Get volume stats
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd info --format json {} / {} " . format ( pool , name_new )
)
2020-02-08 23:35:30 -05:00
volstats = stdout
# 3. Add the new volume to Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . write (
[
( ( " volume " , f " { pool } / { name_new } " ) , " " ) ,
( ( " volume.stats " , f " { pool } / { name_new } " ) , volstats ) ,
( ( " snapshot " , f " { pool } / { name_new } " ) , " " ) ,
]
)
2020-02-08 23:35:30 -05:00
2021-11-06 03:02:43 -04:00
return True , ' Cloned RBD volume " {} " to " {} " in pool " {} " ' . format (
name_src , name_new , pool
)
2019-07-26 14:24:22 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def resize_volume ( zkhandler , pool , name , size ) :
if not verifyVolume ( zkhandler , pool , name ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
name , pool
)
2019-07-26 14:24:22 -04:00
2021-09-12 19:54:51 -04:00
# 1. Verify the size of the volume
pool_information = getPoolInformation ( zkhandler , pool )
size_bytes = format_bytes_fromhuman ( size )
2023-04-28 12:01:11 -04:00
if size_bytes is None :
return (
False ,
f " ERROR: Requested volume size ' { size } ' does not have a valid SI unit " ,
)
2021-11-06 03:02:43 -04:00
if size_bytes > = int ( pool_information [ " stats " ] [ " free_bytes " ] ) :
return (
False ,
2023-04-28 12:01:11 -04:00
f " ERROR: Requested volume size ' { format_bytes_tohuman ( size_bytes ) } ' is greater than the available free space in the pool ( ' { format_bytes_tohuman ( pool_information [ ' stats ' ] [ ' free_bytes ' ] ) } ' ) " ,
2021-11-06 03:02:43 -04:00
)
2021-09-12 19:54:51 -04:00
# 2. Resize the volume
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
2023-04-28 12:01:11 -04:00
" rbd resize --size {} {} / {} " . format (
format_bytes_tohuman ( size_bytes ) , pool , name
)
2021-11-06 03:02:43 -04:00
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: Failed to resize RBD volume " {} " to size " {} " in pool " {} " : {} ' . format (
2023-04-28 12:01:11 -04:00
name , format_bytes_tohuman ( size_bytes ) , pool , stderr
2021-11-06 03:02:43 -04:00
) ,
)
2019-07-26 14:24:22 -04:00
2021-09-12 19:54:51 -04:00
# 3a. Determine the node running this VM if applicable
2020-08-11 21:46:12 -04:00
active_node = None
2021-11-06 03:02:43 -04:00
volume_vm_name = name . split ( " _ " ) [ 0 ]
2021-05-29 20:29:51 -04:00
retcode , vm_info = vm . get_info ( zkhandler , volume_vm_name )
2020-08-11 21:46:12 -04:00
if retcode :
2021-11-06 03:02:43 -04:00
for disk in vm_info [ " disks " ] :
2020-08-11 21:46:12 -04:00
# This block device is present in this VM so we can continue
2021-11-06 03:02:43 -04:00
if disk [ " name " ] == " {} / {} " . format ( pool , name ) :
active_node = vm_info [ " node " ]
volume_id = disk [ " dev " ]
2021-09-12 19:54:51 -04:00
# 3b. Perform a live resize in libvirt if the VM is running
2021-11-06 03:02:43 -04:00
if active_node is not None and vm_info . get ( " state " , " " ) == " start " :
2020-08-11 21:46:12 -04:00
import libvirt
2021-11-06 03:02:43 -04:00
2020-08-11 21:46:12 -04:00
# Run the libvirt command against the target host
try :
2021-11-06 03:02:43 -04:00
dest_lv = " qemu+tcp:// {} /system " . format ( active_node )
2020-08-11 21:46:12 -04:00
target_lv_conn = libvirt . open ( dest_lv )
2021-11-06 03:02:43 -04:00
target_vm_conn = target_lv_conn . lookupByName ( vm_info [ " name " ] )
2020-08-11 21:46:12 -04:00
if target_vm_conn :
2021-11-06 03:02:43 -04:00
target_vm_conn . blockResize (
volume_id ,
2023-04-28 12:01:11 -04:00
size_bytes ,
2021-11-06 03:02:43 -04:00
libvirt . VIR_DOMAIN_BLOCK_RESIZE_BYTES ,
)
2020-08-11 21:46:12 -04:00
target_lv_conn . close ( )
2020-11-06 19:24:10 -05:00
except Exception :
2020-08-11 21:46:12 -04:00
pass
2021-09-12 19:54:51 -04:00
# 4. Get volume stats
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd info --format json {} / {} " . format ( pool , name )
)
2020-02-08 23:35:30 -05:00
volstats = stdout
2019-07-26 14:24:22 -04:00
2021-09-12 19:54:51 -04:00
# 5. Update the volume in Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . write (
[
( ( " volume " , f " { pool } / { name } " ) , " " ) ,
( ( " volume.stats " , f " { pool } / { name } " ) , volstats ) ,
( ( " snapshot " , f " { pool } / { name } " ) , " " ) ,
]
)
2019-07-26 14:24:22 -04:00
2021-11-06 03:02:43 -04:00
return True , ' Resized RBD volume " {} " to size " {} " in pool " {} " . ' . format (
2023-04-28 12:01:11 -04:00
name , format_bytes_tohuman ( size_bytes ) , pool
2021-11-06 03:02:43 -04:00
)
2019-07-26 14:24:22 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def rename_volume ( zkhandler , pool , name , new_name ) :
if not verifyVolume ( zkhandler , pool , name ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
name , pool
)
2018-10-30 22:41:44 -04:00
2020-02-08 23:35:30 -05:00
# 1. Rename the volume
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd rename {} / {} {} " . format ( pool , name , new_name )
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: Failed to rename volume " {} " to " {} " in pool " {} " : {} ' . format (
name , new_name , pool , stderr
) ,
)
2019-06-21 09:05:00 -04:00
2020-02-08 23:35:30 -05:00
# 2. Rename the volume in Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . rename (
[
( ( " volume " , f " { pool } / { name } " ) , ( " volume " , f " { pool } / { new_name } " ) ) ,
( ( " snapshot " , f " { pool } / { name } " ) , ( " snapshot " , f " { pool } / { new_name } " ) ) ,
]
)
2018-10-30 09:17:32 -04:00
2020-02-08 23:35:30 -05:00
# 3. Get volume stats
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd info --format json {} / {} " . format ( pool , new_name )
)
2020-02-08 23:35:30 -05:00
volstats = stdout
2019-10-10 14:09:07 -04:00
2020-02-08 23:35:30 -05:00
# 4. Update the volume stats in Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . write (
[
( ( " volume.stats " , f " { pool } / { new_name } " ) , volstats ) ,
]
)
2019-10-10 14:09:07 -04:00
2021-11-06 03:02:43 -04:00
return True , ' Renamed RBD volume " {} " to " {} " in pool " {} " . ' . format (
name , new_name , pool
)
2019-10-10 14:09:07 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def remove_volume ( zkhandler , pool , name ) :
if not verifyVolume ( zkhandler , pool , name ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
name , pool
)
2018-11-01 22:00:59 -04:00
2020-02-08 23:35:30 -05:00
# 1. Remove volume snapshots
2021-11-06 03:02:43 -04:00
for snapshot in zkhandler . children ( ( " snapshot " , f " { pool } / { name } " ) ) :
2021-05-29 20:29:51 -04:00
remove_snapshot ( zkhandler , pool , name , snapshot )
2018-11-01 22:00:59 -04:00
2020-02-08 23:35:30 -05:00
# 2. Remove the volume
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command ( " rbd rm {} / {} " . format ( pool , name ) )
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: Failed to remove RBD volume " {} " in pool " {} " : {} ' . format (
name , pool , stderr
)
2019-06-21 09:05:00 -04:00
2020-02-08 23:35:30 -05:00
# 3. Delete volume from Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . delete (
[
( " volume " , f " { pool } / { name } " ) ,
( " snapshot " , f " { pool } / { name } " ) ,
]
)
2020-02-08 23:35:30 -05:00
return True , ' Removed RBD volume " {} " in pool " {} " . ' . format ( name , pool )
2018-11-01 22:00:59 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def map_volume ( zkhandler , pool , name ) :
if not verifyVolume ( zkhandler , pool , name ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
name , pool
)
2020-02-09 13:43:48 -05:00
# 1. Map the volume onto the local system
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command ( " rbd map {} / {} " . format ( pool , name ) )
2020-02-09 13:43:48 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: Failed to map RBD volume " {} " in pool " {} " : {} ' . format (
name , pool , stderr
)
2020-02-09 13:43:48 -05:00
# 2. Calculate the absolute path to the mapped volume
2021-11-06 03:02:43 -04:00
mapped_volume = " /dev/rbd/ {} / {} " . format ( pool , name )
2020-02-09 13:43:48 -05:00
# 3. Ensure the volume exists
if not os . path . exists ( mapped_volume ) :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: Mapped volume not found at expected location " {} " . ' . format (
mapped_volume
) ,
)
2020-02-09 13:43:48 -05:00
return True , mapped_volume
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def unmap_volume ( zkhandler , pool , name ) :
if not verifyVolume ( zkhandler , pool , name ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
name , pool
)
2020-02-09 13:43:48 -05:00
2021-11-06 03:02:43 -04:00
mapped_volume = " /dev/rbd/ {} / {} " . format ( pool , name )
2020-02-09 13:43:48 -05:00
# 1. Ensure the volume exists
if not os . path . exists ( mapped_volume ) :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: Mapped volume not found at expected location " {} " . ' . format (
mapped_volume
) ,
)
2020-02-09 13:43:48 -05:00
# 2. Unap the volume
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd unmap {} " . format ( mapped_volume )
)
2020-02-09 13:43:48 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: Failed to unmap RBD volume at " {} " : {} ' . format (
mapped_volume , stderr
)
2020-02-09 13:43:48 -05:00
return True , ' Unmapped RBD volume at " {} " . ' . format ( mapped_volume )
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def get_list_volume ( zkhandler , pool , limit , is_fuzzy = True ) :
if pool and not verifyPool ( zkhandler , pool ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No pool with name " {} " is present in the cluster. ' . format (
pool
)
2018-11-01 22:00:59 -04:00
2021-05-29 20:29:51 -04:00
full_volume_list = getCephVolumes ( zkhandler , pool )
2018-11-01 22:00:59 -04:00
2021-12-06 16:35:29 -05:00
if is_fuzzy and limit :
# Implicitly assume fuzzy limits
if not re . match ( r " \ ^.* " , limit ) :
limit = " .* " + limit
if not re . match ( r " .* \ $ " , limit ) :
limit = limit + " .* "
2019-07-26 15:10:47 -04:00
2021-07-01 17:33:13 -04:00
get_volume_info = dict ( )
2019-07-05 00:29:47 -04:00
for volume in full_volume_list :
2021-11-06 03:02:43 -04:00
pool_name , volume_name = volume . split ( " / " )
2021-07-01 17:33:13 -04:00
is_limit_match = False
# Check on limit
2019-07-05 00:29:47 -04:00
if limit :
2021-07-01 17:33:13 -04:00
# Try to match the limit against the volume name
2019-07-05 00:29:47 -04:00
try :
2021-12-06 16:35:29 -05:00
if re . fullmatch ( limit , volume_name ) :
2021-07-01 17:33:13 -04:00
is_limit_match = True
2019-07-05 00:29:47 -04:00
except Exception as e :
2021-11-06 03:02:43 -04:00
return False , " Regex Error: {} " . format ( e )
2019-07-05 00:29:47 -04:00
else :
2021-07-01 17:33:13 -04:00
is_limit_match = True
get_volume_info [ volume ] = True if is_limit_match else False
# Obtain our volume data in a thread pool
2021-11-06 03:02:43 -04:00
volume_execute_list = [
volume for volume in full_volume_list if get_volume_info [ volume ]
]
2021-07-01 17:33:13 -04:00
volume_data_list = list ( )
2021-11-06 03:02:43 -04:00
with ThreadPoolExecutor (
max_workers = 32 , thread_name_prefix = " volume_list "
) as executor :
2021-07-01 17:33:13 -04:00
futures = [ ]
for volume in volume_execute_list :
2021-11-06 03:02:43 -04:00
pool_name , volume_name = volume . split ( " / " )
futures . append (
executor . submit ( getVolumeInformation , zkhandler , pool_name , volume_name )
)
2021-07-01 17:33:13 -04:00
for future in futures :
volume_data_list . append ( future . result ( ) )
2021-11-06 03:02:43 -04:00
return True , sorted ( volume_data_list , key = lambda x : str ( x [ " name " ] ) )
2018-11-01 22:00:59 -04:00
2018-10-31 23:38:17 -04:00
2019-07-05 00:29:47 -04:00
#
# Snapshot functions
#
2021-05-29 20:29:51 -04:00
def getCephSnapshots ( zkhandler , pool , volume ) :
2019-07-05 00:29:47 -04:00
snapshot_list = list ( )
volume_list = list ( )
2018-10-31 23:38:17 -04:00
2021-05-29 20:29:51 -04:00
volume_list = getCephVolumes ( zkhandler , pool )
2019-07-05 13:57:15 -04:00
if volume :
2019-07-05 00:29:47 -04:00
for volume_entry in volume_list :
2021-11-06 03:02:43 -04:00
volume_pool , volume_name = volume_entry . split ( " / " )
2019-07-05 00:29:47 -04:00
if volume_name == volume :
2021-11-06 03:02:43 -04:00
volume_list = [ " {} / {} " . format ( volume_pool , volume_name ) ]
2018-10-31 23:38:17 -04:00
2019-07-05 00:29:47 -04:00
for volume_entry in volume_list :
2021-11-06 03:02:43 -04:00
for snapshot_name in zkhandler . children ( ( " snapshot " , volume_entry ) ) :
snapshot_list . append ( " {} @ {} " . format ( volume_entry , snapshot_name ) )
2018-10-31 23:38:17 -04:00
2019-07-05 00:29:47 -04:00
return snapshot_list
2018-10-31 23:38:17 -04:00
2020-11-07 14:45:24 -05:00
2023-10-24 00:23:12 -04:00
def add_snapshot ( zkhandler , pool , volume , name , zk_only = False ) :
2021-05-29 20:29:51 -04:00
if not verifyVolume ( zkhandler , pool , volume ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
volume , pool
)
2020-02-08 23:35:30 -05:00
# 1. Create the snapshot
2023-10-24 00:23:12 -04:00
if not zk_only :
retcode , stdout , stderr = common . run_os_command (
" rbd snap create {} / {} @ {} " . format ( pool , volume , name )
2021-11-06 03:02:43 -04:00
)
2023-10-24 00:23:12 -04:00
if retcode :
return (
False ,
' ERROR: Failed to create RBD snapshot " {} " of volume " {} " in pool " {} " : {} ' . format (
name , volume , pool , stderr
) ,
)
2020-02-08 23:35:30 -05:00
# 2. Add the snapshot to Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . write (
[
( ( " snapshot " , f " { pool } / { volume } / { name } " ) , " " ) ,
( ( " snapshot.stats " , f " { pool } / { volume } / { name } " ) , " {} " ) ,
]
)
2020-11-06 19:05:48 -05:00
2021-02-14 17:02:49 -05:00
# 3. Update the count of snapshots on this volume
2021-11-06 03:02:43 -04:00
volume_stats_raw = zkhandler . read ( ( " volume.stats " , f " { pool } / { volume } " ) )
2021-02-14 17:02:49 -05:00
volume_stats = dict ( json . loads ( volume_stats_raw ) )
# Format the size to something nicer
2021-11-06 03:02:43 -04:00
volume_stats [ " snapshot_count " ] = volume_stats [ " snapshot_count " ] + 1
2021-02-14 17:02:49 -05:00
volume_stats_raw = json . dumps ( volume_stats )
2021-11-06 03:02:43 -04:00
zkhandler . write (
[
( ( " volume.stats " , f " { pool } / { volume } " ) , volume_stats_raw ) ,
]
)
2021-02-14 17:02:49 -05:00
2021-11-06 03:02:43 -04:00
return True , ' Created RBD snapshot " {} " of volume " {} " in pool " {} " . ' . format (
name , volume , pool
)
2019-07-28 23:00:35 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def rename_snapshot ( zkhandler , pool , volume , name , new_name ) :
if not verifyVolume ( zkhandler , pool , volume ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
volume , pool
)
2021-05-29 20:29:51 -04:00
if not verifySnapshot ( zkhandler , pool , volume , name ) :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: No snapshot with name " {} " is present for volume " {} " in pool " {} " . ' . format (
name , volume , pool
) ,
)
2019-07-28 23:00:35 -04:00
2020-02-08 23:35:30 -05:00
# 1. Rename the snapshot
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd snap rename {pool} / {volume} @ {name} {pool} / {volume} @ {new_name} " . format (
pool = pool , volume = volume , name = name , new_name = new_name
)
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: Failed to rename RBD snapshot " {} " to " {} " for volume " {} " in pool " {} " : {} ' . format (
name , new_name , volume , pool , stderr
) ,
)
2019-06-19 00:12:44 -04:00
2020-02-08 23:35:30 -05:00
# 2. Rename the snapshot in ZK
2021-11-06 03:02:43 -04:00
zkhandler . rename (
[
(
( " snapshot " , f " { pool } / { volume } / { name } " ) ,
( " snapshot " , f " { pool } / { volume } / { new_name } " ) ,
) ,
]
)
return (
True ,
' Renamed RBD snapshot " {} " to " {} " for volume " {} " in pool " {} " . ' . format (
name , new_name , volume , pool
) ,
)
2019-06-19 00:12:44 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def remove_snapshot ( zkhandler , pool , volume , name ) :
if not verifyVolume ( zkhandler , pool , volume ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No volume with name " {} " is present in pool " {} " . ' . format (
volume , pool
)
2021-05-29 20:29:51 -04:00
if not verifySnapshot ( zkhandler , pool , volume , name ) :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: No snapshot with name " {} " is present of volume {} in pool {} . ' . format (
name , volume , pool
) ,
)
2019-06-19 00:12:44 -04:00
2020-02-08 23:35:30 -05:00
# 1. Remove the snapshot
2021-11-06 03:02:43 -04:00
retcode , stdout , stderr = common . run_os_command (
" rbd snap rm {} / {} @ {} " . format ( pool , volume , name )
)
2020-02-08 23:35:30 -05:00
if retcode :
2021-11-06 03:02:43 -04:00
return (
False ,
' Failed to remove RBD snapshot " {} " of volume " {} " in pool " {} " : {} ' . format (
name , volume , pool , stderr
) ,
)
2019-06-19 00:12:44 -04:00
2020-02-08 23:35:30 -05:00
# 2. Delete snapshot from Zookeeper
2021-11-06 03:02:43 -04:00
zkhandler . delete ( [ ( " snapshot " , f " { pool } / { volume } / { name } " ) ] )
2019-06-19 00:12:44 -04:00
2021-02-14 17:02:49 -05:00
# 3. Update the count of snapshots on this volume
2021-11-06 03:02:43 -04:00
volume_stats_raw = zkhandler . read ( ( " volume.stats " , f " { pool } / { volume } " ) )
2021-02-14 17:02:49 -05:00
volume_stats = dict ( json . loads ( volume_stats_raw ) )
# Format the size to something nicer
2021-11-06 03:02:43 -04:00
volume_stats [ " snapshot_count " ] = volume_stats [ " snapshot_count " ] - 1
2021-02-14 17:02:49 -05:00
volume_stats_raw = json . dumps ( volume_stats )
2021-11-06 03:02:43 -04:00
zkhandler . write ( [ ( ( " volume.stats " , f " { pool } / { volume } " ) , volume_stats_raw ) ] )
2021-02-14 17:02:49 -05:00
2021-11-06 03:02:43 -04:00
return True , ' Removed RBD snapshot " {} " of volume " {} " in pool " {} " . ' . format (
name , volume , pool
)
2019-06-19 00:12:44 -04:00
2020-11-07 14:45:24 -05:00
2021-05-29 20:29:51 -04:00
def get_list_snapshot ( zkhandler , pool , volume , limit , is_fuzzy = True ) :
2019-07-05 00:29:47 -04:00
snapshot_list = [ ]
2021-05-29 20:29:51 -04:00
if pool and not verifyPool ( zkhandler , pool ) :
2021-11-06 03:02:43 -04:00
return False , ' ERROR: No pool with name " {} " is present in the cluster. ' . format (
pool
)
2019-06-19 15:32:32 -04:00
2021-05-29 20:29:51 -04:00
if volume and not verifyPool ( zkhandler , volume ) :
2021-11-06 03:02:43 -04:00
return (
False ,
' ERROR: No volume with name " {} " is present in the cluster. ' . format ( volume ) ,
)
2019-07-04 23:09:16 -04:00
2021-05-29 20:29:51 -04:00
full_snapshot_list = getCephSnapshots ( zkhandler , pool , volume )
2019-07-04 23:09:16 -04:00
2019-07-26 15:03:48 -04:00
if is_fuzzy and limit :
# Implicitly assume fuzzy limits
2021-11-06 03:02:43 -04:00
if not re . match ( r " \ ^.* " , limit ) :
limit = " .* " + limit
if not re . match ( r " .* \ $ " , limit ) :
limit = limit + " .* "
2019-07-26 15:03:48 -04:00
2019-07-05 00:29:47 -04:00
for snapshot in full_snapshot_list :
2021-11-06 03:02:43 -04:00
volume , snapshot_name = snapshot . split ( " @ " )
pool_name , volume_name = volume . split ( " / " )
2019-07-05 00:29:47 -04:00
if limit :
try :
2021-12-06 16:35:29 -05:00
if re . fullmatch ( limit , snapshot_name ) :
2021-11-06 03:02:43 -04:00
snapshot_list . append (
{
" pool " : pool_name ,
" volume " : volume_name ,
" snapshot " : snapshot_name ,
}
)
2019-07-05 00:29:47 -04:00
except Exception as e :
2021-11-06 03:02:43 -04:00
return False , " Regex Error: {} " . format ( e )
2019-07-05 00:29:47 -04:00
else :
2021-11-06 03:02:43 -04:00
snapshot_list . append (
{ " pool " : pool_name , " volume " : volume_name , " snapshot " : snapshot_name }
)
2019-07-04 23:09:16 -04:00
2021-11-06 03:02:43 -04:00
return True , sorted ( snapshot_list , key = lambda x : str ( x [ " snapshot " ] ) )