use serde::{Deserialize, Serialize};
use super::PlacementTypeError;
use super::capability::{
AttrValue, Capability, CapabilityId, CapabilityState, Provenance, valid_attr_name,
validate_dotted,
};
pub const MAX_PREDICATES: usize = 32;
pub const MAX_COUNT: u32 = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IdSelector {
Exact(CapabilityId),
Prefix(String),
}
impl IdSelector {
pub fn selects(&self, id: &CapabilityId) -> bool {
match self {
IdSelector::Exact(want) => want == id,
IdSelector::Prefix(p) => id.is_under(p),
}
}
pub fn as_str(&self) -> &str {
match self {
IdSelector::Exact(id) => id.as_str(),
IdSelector::Prefix(p) => p,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PredicateOp {
Eq,
Gte,
Lte,
In,
Has,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(try_from = "PredicateRaw")]
pub struct AttrPredicate {
pub attr: String,
pub op: PredicateOp,
pub value: AttrValue,
}
#[derive(Serialize)]
struct PredicateOut<'a> {
attr: &'a str,
op: PredicateOp,
value: &'a AttrValue,
}
impl Serialize for AttrPredicate {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.validate().map_err(serde::ser::Error::custom)?;
PredicateOut {
attr: &self.attr,
op: self.op,
value: &self.value,
}
.serialize(s)
}
}
#[derive(Deserialize)]
struct PredicateRaw {
attr: String,
op: PredicateOp,
value: AttrValue,
}
impl TryFrom<PredicateRaw> for AttrPredicate {
type Error = PlacementTypeError;
fn try_from(r: PredicateRaw) -> Result<Self, Self::Error> {
let p = AttrPredicate {
attr: r.attr,
op: r.op,
value: r.value,
};
p.validate()?;
Ok(p)
}
}
impl AttrPredicate {
fn mk(attr: &str, op: PredicateOp, value: AttrValue) -> Self {
Self {
attr: attr.to_string(),
op,
value,
}
}
pub fn eq(attr: &str, value: impl Into<AttrValue>) -> Self {
Self::mk(attr, PredicateOp::Eq, value.into())
}
pub fn gte(attr: &str, value: f64) -> Self {
Self::mk(attr, PredicateOp::Gte, AttrValue::Float(value))
}
pub fn lte(attr: &str, value: f64) -> Self {
Self::mk(attr, PredicateOp::Lte, AttrValue::Float(value))
}
pub fn is_in(attr: &str, values: Vec<AttrValue>) -> Self {
Self::mk(attr, PredicateOp::In, AttrValue::List(values))
}
pub fn has(attr: &str, value: impl Into<AttrValue>) -> Self {
Self::mk(attr, PredicateOp::Has, value.into())
}
pub fn validate(&self) -> Result<(), PlacementTypeError> {
let bad = |why: &str| PlacementTypeError::InvalidPredicate {
attr: self.attr.chars().take(64).collect(),
reason: why.to_string(),
};
if !valid_attr_name(&self.attr) {
return Err(bad("attribute name must match [a-z][a-z0-9_]*"));
}
self.value.validate(0).map_err(|why| bad(&why))?;
match (self.op, &self.value) {
(PredicateOp::Gte | PredicateOp::Lte, v) if v.as_f64().is_none() => {
Err(bad("gte/lte need a numeric value"))
}
(PredicateOp::In, v) if !matches!(v, AttrValue::List(_)) => {
Err(bad("in needs a list value"))
}
(PredicateOp::Has, AttrValue::List(_)) => Err(bad("has needs a scalar value")),
_ => Ok(()),
}
}
pub fn eval(&self, cap: &Capability) -> bool {
let Some(have) = cap.attrs.get(&self.attr) else {
return false;
};
match self.op {
PredicateOp::Eq => have.loose_eq(&self.value),
PredicateOp::Gte => cmp_num(have, &self.value, |a, b| a >= b),
PredicateOp::Lte => cmp_num(have, &self.value, |a, b| a <= b),
PredicateOp::In => match &self.value {
AttrValue::List(opts) => opts.iter().any(|o| have.loose_eq(o)),
_ => false,
},
PredicateOp::Has => match have {
AttrValue::List(items) => items.iter().any(|i| i.loose_eq(&self.value)),
_ => false,
},
}
}
}
fn cmp_num(a: &AttrValue, b: &AttrValue, f: impl Fn(f64, f64) -> bool) -> bool {
matches!((a.as_f64(), b.as_f64()), (Some(x), Some(y)) if f(x, y))
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(try_from = "RequirementRaw")]
pub struct Requirement {
pub selector: IdSelector,
pub where_: Vec<AttrPredicate>,
pub count: u32,
pub exclusive: bool,
pub min_provenance: Option<Provenance>,
}
#[derive(Serialize, Deserialize)]
struct RequirementRaw {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
id_prefix: Option<String>,
#[serde(rename = "where", default, skip_serializing_if = "Vec::is_empty")]
where_: Vec<AttrPredicate>,
#[serde(default = "one")]
count: u32,
#[serde(default)]
exclusive: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
min_provenance: Option<Provenance>,
}
fn one() -> u32 {
1
}
impl TryFrom<RequirementRaw> for Requirement {
type Error = PlacementTypeError;
fn try_from(r: RequirementRaw) -> Result<Self, Self::Error> {
let selector = match (r.id, r.id_prefix) {
(Some(id), None) => IdSelector::Exact(CapabilityId::new(id)?),
(None, Some(p)) => {
validate_dotted(&p, 1)?;
IdSelector::Prefix(p)
}
_ => {
return Err(PlacementTypeError::InvalidRequirement(
"exactly one of `id` or `id_prefix` is required".into(),
));
}
};
let req = Requirement {
selector,
where_: r.where_,
count: r.count,
exclusive: r.exclusive,
min_provenance: r.min_provenance,
};
req.validate()?;
Ok(req)
}
}
impl Serialize for Requirement {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.validate().map_err(serde::ser::Error::custom)?;
let (id, id_prefix) = match &self.selector {
IdSelector::Exact(id) => (Some(id.to_string()), None),
IdSelector::Prefix(p) => (None, Some(p.clone())),
};
RequirementRaw {
id,
id_prefix,
where_: self.where_.clone(),
count: self.count,
exclusive: self.exclusive,
min_provenance: self.min_provenance,
}
.serialize(s)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum MatchFailure {
NoSuchId {
selector: String,
},
ProvenanceTooLow {
best: Provenance,
need: Provenance,
},
PredicateFailed {
attr: String,
op: PredicateOp,
},
Unavailable {
state: CapabilityState,
},
InsufficientCount {
have: u32,
need: u32,
},
Contended {
need: u32,
},
}
impl Requirement {
pub fn exact(id: CapabilityId) -> Self {
Self {
selector: IdSelector::Exact(id),
where_: vec![],
count: 1,
exclusive: false,
min_provenance: None,
}
}
pub fn prefix(prefix: &str) -> Result<Self, PlacementTypeError> {
validate_dotted(prefix, 1)?;
Ok(Self {
selector: IdSelector::Prefix(prefix.to_string()),
where_: vec![],
count: 1,
exclusive: false,
min_provenance: None,
})
}
pub fn with_where(mut self, p: AttrPredicate) -> Self {
self.where_.push(p);
self
}
pub fn try_with_where(self, p: AttrPredicate) -> Result<Self, PlacementTypeError> {
let r = self.with_where(p);
r.validate()?;
Ok(r)
}
pub fn with_count(mut self, count: u32) -> Self {
self.count = count;
self
}
pub fn exclusive(mut self) -> Self {
self.exclusive = true;
self
}
pub fn with_min_provenance(mut self, p: Provenance) -> Self {
self.min_provenance = Some(p);
self
}
pub fn validate(&self) -> Result<(), PlacementTypeError> {
if let IdSelector::Prefix(p) = &self.selector {
validate_dotted(p, 1)?;
}
if self.count == 0 || self.count > MAX_COUNT {
return Err(PlacementTypeError::InvalidRequirement(format!(
"count must be 1..={MAX_COUNT}"
)));
}
if self.where_.len() > MAX_PREDICATES {
return Err(PlacementTypeError::InvalidRequirement(
"too many predicates".into(),
));
}
self.where_.iter().try_for_each(AttrPredicate::validate)
}
fn usable(&self, cap: &Capability) -> bool {
match cap.state {
CapabilityState::Available => true,
CapabilityState::Busy => !self.exclusive && !cap.exclusive,
CapabilityState::Reserved | CapabilityState::Degraded => false,
}
}
pub fn match_caps(&self, caps: &[Capability]) -> Result<Vec<usize>, MatchFailure> {
let usable = self.candidates(caps)?;
let need = self.count as usize;
if usable.len() < need {
return Err(MatchFailure::InsufficientCount {
have: usable.len() as u32,
need: self.count,
});
}
Ok(usable.into_iter().take(need).collect())
}
pub fn candidates(&self, caps: &[Capability]) -> Result<Vec<usize>, MatchFailure> {
let by_id: Vec<usize> = (0..caps.len())
.filter(|&i| self.selector.selects(&caps[i].id))
.collect();
if by_id.is_empty() {
return Err(MatchFailure::NoSuchId {
selector: self.selector.as_str().to_string(),
});
}
let by_prov: Vec<usize> = match self.min_provenance {
Some(need) => {
let v: Vec<usize> = by_id
.iter()
.copied()
.filter(|&i| caps[i].provenance >= need)
.collect();
if v.is_empty() {
let best = by_id
.iter()
.map(|&i| caps[i].provenance)
.max()
.unwrap_or(Provenance::Claimed);
return Err(MatchFailure::ProvenanceTooLow { best, need });
}
v
}
None => by_id,
};
let by_attr: Vec<usize> = by_prov
.iter()
.copied()
.filter(|&i| self.where_.iter().all(|p| p.eval(&caps[i])))
.collect();
if by_attr.is_empty() {
let first = &caps[by_prov[0]];
let p = self
.where_
.iter()
.find(|p| !p.eval(first))
.expect("some predicate failed");
return Err(MatchFailure::PredicateFailed {
attr: p.attr.clone(),
op: p.op,
});
}
let usable: Vec<usize> = by_attr
.iter()
.copied()
.filter(|&i| self.usable(&caps[i]))
.collect();
if usable.is_empty() {
return Err(MatchFailure::Unavailable {
state: caps[by_attr[0]].state,
});
}
Ok(usable)
}
pub fn matches(&self, caps: &[Capability]) -> bool {
self.match_caps(caps).is_ok()
}
}