Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.