///|
/// Result of examining one stripe without returning payload bytes.
pub(all) enum StripeStatus {
Healthy
Repairable
Unrecoverable
InvalidInput
}
///|
pub fn StripeStatus::label(self : StripeStatus) -> String {
match self {
Healthy => "healthy"
Repairable => "repairable"
Unrecoverable => "unrecoverable"
InvalidInput => "invalid-input"
}
}
///|
pub struct StripeHealth {
stripe_index : Int
status : StripeStatus
present_count : Int
missing_indices : Array[Int]
reason_code : String?
}
///|
pub fn StripeHealth::stripe_index(self : StripeHealth) -> Int {
self.stripe_index
}
///|
pub fn StripeHealth::status(self : StripeHealth) -> StripeStatus {
self.status
}
///|
pub fn StripeHealth::present_count(self : StripeHealth) -> Int {
self.present_count
}
///|
pub fn StripeHealth::missing_indices(self : StripeHealth) -> Array[Int] {
let copy : Array[Int] = []
for index in self.missing_indices {
copy.push(index)
}
copy
}
///|
pub fn StripeHealth::reason_code(self : StripeHealth) -> String? {
self.reason_code
}
///|
fn invalid_health(
stripe_index : Int,
present_count : Int,
missing_indices : Array[Int],
reason_code : String,
) -> StripeHealth {
{
stripe_index,
status: InvalidInput,
present_count,
missing_indices,
reason_code: Some(reason_code),
}
}
///|
/// Plan an individual stripe and verify consistency when enough frames exist.
pub fn inspect_stripe(
codec : Codec,
frames : Array[ShardEnvelope],
set_id : Int,
stripe_index : Int,
) -> StripeHealth {
let present = Array::make(codec.total_count(), false)
let mut reason_code : String? = None
let mut present_count = 0
for frame in frames {
let index = frame.shard_index()
if frame.set_id() != set_id ||
frame.stripe_index() != stripe_index ||
frame.data_count() != codec.data_count() ||
frame.parity_count() != codec.parity_count() {
reason_code = Some("ERASURE_ENVELOPE")
} else if index < 0 || index >= codec.total_count() {
reason_code = Some("ERASURE_INDEX")
} else if present[index] {
reason_code = Some("ERASURE_DUPLICATE_INDEX")
} else {
present[index] = true
present_count = present_count + 1
}
}
let missing_indices : Array[Int] = []
for index = 0; index < present.length(); index = index + 1 {
if !present[index] {
missing_indices.push(index)
}
}
match reason_code {
Some(code) =>
return invalid_health(stripe_index, present_count, missing_indices, code)
None => ()
}
if present_count < codec.data_count() {
return {
stripe_index,
status: Unrecoverable,
present_count,
missing_indices,
reason_code: Some("ERASURE_NOT_ENOUGH_SHARDS"),
}
}
let checked = try recover_stripe(codec, frames, set_id, stripe_index) catch {
error => Some(error.code())
} noraise {
_ => None
}
match checked {
Some(code) =>
invalid_health(stripe_index, present_count, missing_indices, code)
None =>
{
stripe_index,
status: if missing_indices.length() == 0 {
Healthy
} else {
Repairable
},
present_count,
missing_indices,
reason_code: None,
}
}
}
///|
pub struct ObjectHealth {
stripes : Array[StripeHealth]
healthy_count : Int
repairable_count : Int
unrecoverable_count : Int
invalid_count : Int
}
///|
pub fn ObjectHealth::stripes(self : ObjectHealth) -> Array[StripeHealth] {
let copy : Array[StripeHealth] = []
for stripe in self.stripes {
copy.push(stripe)
}
copy
}
///|
pub fn ObjectHealth::healthy_count(self : ObjectHealth) -> Int {
self.healthy_count
}
///|
pub fn ObjectHealth::repairable_count(self : ObjectHealth) -> Int {
self.repairable_count
}
///|
pub fn ObjectHealth::unrecoverable_count(self : ObjectHealth) -> Int {
self.unrecoverable_count
}
///|
pub fn ObjectHealth::invalid_count(self : ObjectHealth) -> Int {
self.invalid_count
}
///|
pub fn ObjectHealth::fully_recoverable(self : ObjectHealth) -> Bool {
self.unrecoverable_count == 0 && self.invalid_count == 0
}
///|
/// Inspect every stripe against a manifest. The caller may decide which
/// repairable stripes to reconstruct and where to place replacement shards.
pub fn inspect_object(
manifest : Manifest,
frames : Array[ShardEnvelope],
max_encoded_bytes? : Int = 16_777_216,
) -> ObjectHealth raise ErasureError {
let codec = Codec::new(
manifest.data_count(),
manifest.parity_count(),
max_encoded_bytes~,
)
if manifest.stripe_payload_limit() >
codec.data_count() * codec.max_shard_bytes() {
raise ResourceLimit("codec budget cannot hold manifest stripe")
}
if frames.length() > manifest.stripe_count() * codec.total_count() {
raise ResourceLimit("too many frames for manifest")
}
let groups : Array[Array[ShardEnvelope]] = []
for index = 0; index < manifest.stripe_count(); index = index + 1 {
groups.push([])
}
for frame in frames {
let index = frame.stripe_index()
if index < 0 || index >= manifest.stripe_count() {
raise InvalidIndex(index)
}
groups[index].push(frame)
}
let stripes : Array[StripeHealth] = []
let mut healthy_count = 0
let mut repairable_count = 0
let mut unrecoverable_count = 0
let mut invalid_count = 0
for index = 0; index < groups.length(); index = index + 1 {
let report = inspect_stripe(codec, groups[index], manifest.set_id(), index)
match report.status() {
Healthy => healthy_count = healthy_count + 1
Repairable => repairable_count = repairable_count + 1
Unrecoverable => unrecoverable_count = unrecoverable_count + 1
InvalidInput => invalid_count = invalid_count + 1
}
stripes.push(report)
}
{
stripes,
healthy_count,
repairable_count,
unrecoverable_count,
invalid_count,
}
}