/btrfs_convert_raid6.py
Created Mar 4, 2019
Convert a almost full raid1 btrfs to a raid6
| """ Script to convert a BTRFS filesystem to a raid6 | |
| WARNING: hacky python2 script use with caution, modify to for you setup etc. | |
| The problem is that balance isn't smart about where it moves chunks from | |
| so it empties one disk first. Problem being the other remain fill up with raid1 + new raid6. | |
| Then new raid6 gets striped over fewer drives, which can also take up more space than raid1 if 3-way. | |
| This script balances the balance so that a proper raid6 over all drives is made. | |
| We poll btrfs device usage to find out when drives are running low. | |
| Then use dconvert filtered by profiles and devid to force data to be moved from the | |
| required disk. | |
| """ | |
| import subprocess | |
| from time import sleep | |
| # Things to define | |
| MOUNT="/media/Kilo" | |
| FROM="Data,RAID1" # Target data to check only drives with data are considered | |
| BALANCE_CONVERT="-dconvert=raid6,profiles=raid1" # Balance command excluding devid | |
| THRESHOLD=1200000000 # About 1GiB | |
| BTRFS_TOOL="/bin/btrfs" | |
| def read_usage(): | |
| stats = subprocess.check_output([BTRFS_TOOL, "device", "usage", "--raw", MOUNT]) | |
| devices = stats.split("\n\n") | |
| if FROM not in stats: | |
| print "We are done" | |
| exit(0) | |
| worst = None | |
| worst_drive = None | |
| for drive in stats.split("\n\n"): | |
| if FROM not in drive: # No data left to convert, cannot balance this drive | |
| continue | |
| lines = drive.split("\n") | |
| device_path, _, device_id = lines[0].split() | |
| device_path = device_path[:-1] | |
| for line in lines[1:]: | |
| if "Unallocated:" in line: | |
| if worst is None or int(line.split()[1]) < worst: | |
| worst = int(line.split()[1]) | |
| worst_drive = device_id | |
| # Assume only one device_id | |
| break | |
| return worst, worst_drive | |
| balance_pid = None | |
| def start_balance(device): | |
| global balance_pid | |
| device = str(device) | |
| balance_pid = subprocess.Popen([BTRFS_TOOL, "balance", "start", BALANCE_CONVERT + ",devid=" + device, MOUNT]) | |
| def cancel_balance(): | |
| global balance_pid | |
| ret = subprocess.call([BTRFS_TOOL, "balance", "cancel", MOUNT]) | |
| balance_pid.wait() | |
| assert ret == 0 | |
| def main(): | |
| worst, worst_drive = read_usage() | |
| print "Starting balance on", worst_drive, worst | |
| start_balance(worst_drive) | |
| last_worst = worst_drive | |
| while True: # The exit condition lives in read_usage | |
| sleep(10) | |
| worst, worst_drive = read_usage() | |
| if worst_drive != last_worst and worst < THRESHOLD: | |
| print "Almost full drive found", worst_drive | |
| print "Stopping balance on", last_worst | |
| cancel_balance() | |
| print "Starting balance on", worst_drive, worst | |
| start_balance(worst_drive) | |
| last_worst = worst_drive | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment