Skip to content

Commit

Permalink
Enhance Pool size calculation re new raid levels rockstor#2525
Browse files Browse the repository at this point in the history
Move static elements of raid levels to our overall PROFILE
var and do minor refactor of remaining size calculation
code to clarify parameters involved.
Improve flexibility re hard-wired chunk/stripe setting:
here we sacrifice some speed for flexibility.
  • Loading branch information
phillxnet committed Apr 19, 2023
1 parent e490c32 commit 00eecd7
Showing 1 changed file with 131 additions and 39 deletions.
170 changes: 131 additions & 39 deletions src/rockstor/fs/btrfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
BalanceStatusAll = collections.namedtuple("BalanceStatusAll", "active internal status")
# Named Tuple to define raid profile limits and data/metadata
btrfs_profile = collections.namedtuple(
"btrfs_profile", "min_dev_count max_dev_missing data_raid metadata_raid"
"btrfs_profile",
"min_dev_count max_dev_missing data_raid metadata_raid data_copies data_parity",
)
# List of Rockstor btrfs raid profiles indexed by their name.
# I.e. PROFILE[raid_level].min_dev_count
Expand All @@ -115,64 +116,154 @@
# We specify a min dev count of 4 to account for any raid level,
# and likewise play safe by allowing for no missing devices.
"unknown": btrfs_profile(
min_dev_count=4, max_dev_missing=0, data_raid="unknown", metadata_raid="unknown"
min_dev_count=4,
max_dev_missing=0,
data_raid="unknown",
metadata_raid="unknown",
data_copies=1,
data_parity=0,
),
# non redundant profiles!
"single": btrfs_profile(
min_dev_count=1, max_dev_missing=0, data_raid="single", metadata_raid="single"
min_dev_count=1,
max_dev_missing=0,
data_raid="single",
metadata_raid="single",
data_copies=1,
data_parity=0,
),
"single-dup": btrfs_profile(
min_dev_count=1, max_dev_missing=0, data_raid="single", metadata_raid="dup"
min_dev_count=1,
max_dev_missing=0,
data_raid="single",
metadata_raid="dup",
data_copies=1,
data_parity=0,
),
"raid0": btrfs_profile(
min_dev_count=2, max_dev_missing=0, data_raid="raid0", metadata_raid="raid0"
min_dev_count=2,
max_dev_missing=0,
data_raid="raid0",
metadata_raid="raid0",
data_copies=1,
data_parity=0,
),
# Mirrored profiles:
"raid1": btrfs_profile(
min_dev_count=2, max_dev_missing=1, data_raid="raid1", metadata_raid="raid1"
min_dev_count=2,
max_dev_missing=1,
data_raid="raid1",
metadata_raid="raid1",
data_copies=2,
data_parity=0,
),
"raid1c3": btrfs_profile(
min_dev_count=3, max_dev_missing=2, data_raid="raid1c3", metadata_raid="raid1c3"
min_dev_count=3,
max_dev_missing=2,
data_raid="raid1c3",
metadata_raid="raid1c3",
data_copies=3,
data_parity=0,
),
"raid1c4": btrfs_profile(
min_dev_count=4, max_dev_missing=3, data_raid="raid1c4", metadata_raid="raid1c4"
min_dev_count=4,
max_dev_missing=3,
data_raid="raid1c4",
metadata_raid="raid1c4",
data_copies=4,
data_parity=0,
),
"raid10": btrfs_profile(
min_dev_count=4, max_dev_missing=1, data_raid="raid10", metadata_raid="raid10"
min_dev_count=4,
max_dev_missing=1,
data_raid="raid10",
metadata_raid="raid10",
data_copies=2,
data_parity=0,
),
# Parity raid levels (recommended min_dev_count is 3 & 4 respectively)
"raid5": btrfs_profile(
min_dev_count=2, max_dev_missing=1, data_raid="raid5", metadata_raid="raid5"
min_dev_count=2,
max_dev_missing=1,
data_raid="raid5",
metadata_raid="raid5",
data_copies=1,
data_parity=1,
),
"raid6": btrfs_profile(
min_dev_count=3, max_dev_missing=2, data_raid="raid6", metadata_raid="raid6"
min_dev_count=3,
max_dev_missing=2,
data_raid="raid6",
metadata_raid="raid6",
data_copies=1,
data_parity=2,
),
# ------- MIXED PROFILES DATA-METADATA (max 10 chars) -------
# Mixed Mirrored profiles:
"raid1-1c3": btrfs_profile(
min_dev_count=3, max_dev_missing=1, data_raid="raid1", metadata_raid="raid1c3"
min_dev_count=3,
max_dev_missing=1,
data_raid="raid1",
metadata_raid="raid1c3",
data_copies=2,
data_parity=0,
),
"raid1-1c4": btrfs_profile(
min_dev_count=4, max_dev_missing=1, data_raid="raid1", metadata_raid="raid1c4"
min_dev_count=4,
max_dev_missing=1,
data_raid="raid1",
metadata_raid="raid1c4",
data_copies=2,
data_parity=0,
),
"raid10-1c3": btrfs_profile(
min_dev_count=4, max_dev_missing=1, data_raid="raid10", metadata_raid="raid1c3"
min_dev_count=4,
max_dev_missing=1,
data_raid="raid10",
metadata_raid="raid1c3",
data_copies=2,
data_parity=0,
),
"raid10-1c4": btrfs_profile(
min_dev_count=4, max_dev_missing=1, data_raid="raid10", metadata_raid="raid1c4"
min_dev_count=4,
max_dev_missing=1,
data_raid="raid10",
metadata_raid="raid1c4",
data_copies=2,
data_parity=0,
),
# Parity data - Mirrored metadata
"raid5-1": btrfs_profile(
min_dev_count=2, max_dev_missing=1, data_raid="raid5", metadata_raid="raid1"
min_dev_count=2,
max_dev_missing=1,
data_raid="raid5",
metadata_raid="raid1",
data_copies=1,
data_parity=1,
),
"raid5-1c3": btrfs_profile(
min_dev_count=3, max_dev_missing=1, data_raid="raid5", metadata_raid="raid1c3"
min_dev_count=3,
max_dev_missing=1,
data_raid="raid5",
metadata_raid="raid1c3",
data_copies=1,
data_parity=1,
),
"raid6-1c3": btrfs_profile(
min_dev_count=3, max_dev_missing=2, data_raid="raid6", metadata_raid="raid1c3"
min_dev_count=3,
max_dev_missing=2,
data_raid="raid6",
metadata_raid="raid1c3",
data_copies=1,
data_parity=2,
),
"raid6-1c4": btrfs_profile(
min_dev_count=4, max_dev_missing=2, data_raid="raid6", metadata_raid="raid1c4"
min_dev_count=4,
max_dev_missing=2,
data_raid="raid6",
metadata_raid="raid1c4",
data_copies=1,
data_parity=2,
),
}

Expand Down Expand Up @@ -1713,38 +1804,39 @@ def usage_bound(disk_sizes, num_devices, raid_level):
"""Return the total amount of storage possible within this pool's set
of disks, in bytes.
Algorithm adapted from Hugo Mills' implementation at:
Algorithm adapted from Hugo Mills' implementation previously at:
http://carfax.org.uk/btrfs-usage/js/btrfs-usage.js
and replaced by the elm generated: https://carfax.org.uk/btrfs-usage/elm.js
"""
# Determine RAID parameters
data_ratio = 1
stripes = 1
parity = 0
data_copies = PROFILE[raid_level].data_copies
data_parity = PROFILE[raid_level].data_parity

# TODO: As stripes/chunks depend on pool geometry we cannot use PROFILE.
# Once we are Python 3 compatible, re-assess our size calculation in light of
# https://github.com/knorrie/python-btrfs i.e.:
# btrfs-space-calculator
stripes = 1
# Number of chunks to write at a time: as many as possible within the
# number of stripes
chunks = num_devices

if raid_level == "single":
if raid_level.startswith(("unknown", "single")):
chunks = 1
elif raid_level == "raid0":
# We have no mixed raid levels with raid0, but in case we encounter them:
elif raid_level.startswith("raid0"):
stripes = 2
elif raid_level == "raid1":
data_ratio = 2
# Take care not to match raid10 before its time:
elif raid_level == "raid1" or raid_level.startswith("raid1-"):
chunks = 2
elif raid_level == "raid10":
data_ratio = 2
elif raid_level.startswith("raid10"):
stripes = max(2, int(num_devices / 2))
elif raid_level == "raid5":
parity = 1
elif raid_level == "raid6":
parity = 2

# Round down so that we have an exact number of duplicate copies
chunks -= chunks % data_ratio
chunks -= chunks % data_copies

# Check for feasibility at the lower end
if num_devices < data_ratio * (stripes + parity):
if num_devices < data_copies * (stripes + data_parity):
return 0

# Compute the trivial bound
Expand All @@ -1754,8 +1846,8 @@ def usage_bound(disk_sizes, num_devices, raid_level):
# modify the trivial bound if it passes.
bounding_q = -1
for q in range(chunks - 1):
slice = sum(disk_sizes[q + 1 :])
b = int(slice / (chunks - q - 1))
slice_value = sum(disk_sizes[q + 1 :])
b = int(slice_value / (chunks - q - 1))
if disk_sizes[q] >= b and b < bound:
bound = b
bounding_q = q
Expand All @@ -1764,7 +1856,7 @@ def usage_bound(disk_sizes, num_devices, raid_level):
# have no bounding_q, then we have hit the trivial bound, and exhausted
# all space, so we can return immediately.
if bounding_q == -1:
return bound * ((chunks / data_ratio) - parity)
return bound * ((chunks / data_copies) - data_parity)

# If we have a bounding_q, then all the devices past q are full, and
# we can remove them. The devices up to q have been used in every one
Expand All @@ -1775,7 +1867,7 @@ def usage_bound(disk_sizes, num_devices, raid_level):

new_bound = usage_bound(disk_sizes, bounding_q + 1, raid_level)

return bound * ((chunks / data_ratio) - parity) + new_bound
return bound * ((chunks / data_copies) - data_parity) + new_bound


def scrub_start(pool, force=False):
Expand Down

0 comments on commit 00eecd7

Please sign in to comment.