|
| 1 | +defmodule Hyper.Node.Budget.NodeStatePropertiesTest do |
| 2 | + @moduledoc """ |
| 3 | + Monotonicity laws of the pure `NodeState.fits?/2` predicate, complementing the |
| 4 | + exact-`<=`-boundary example tests. A spec strictly within every ceiling always |
| 5 | + fits, exceeding free memory always fails, and a spec that fits a node still |
| 6 | + fits a node with strictly more headroom. |
| 7 | + """ |
| 8 | + use ExUnit.Case, async: true |
| 9 | + use ExUnitProperties |
| 10 | + use Unit.Operators |
| 11 | + |
| 12 | + alias Hyper.Node.Budget.NodeState |
| 13 | + alias Hyper.Vm.Instance.Spec |
| 14 | + alias Unit.{Bandwidth, Information} |
| 15 | + |
| 16 | + # A node, idle on every soft metric with `cpu_max_load` at 1.0, so the only |
| 17 | + # binding limits are the generated hard headrooms and cpu capacity. |
| 18 | + defp state do |
| 19 | + gen all( |
| 20 | + mem_gib <- integer(1..64), |
| 21 | + disk_gib <- integer(1..1000), |
| 22 | + cpu_cap <- integer(1..128) |
| 23 | + ) do |
| 24 | + %NodeState{ |
| 25 | + node: :n@h, |
| 26 | + mem_free: Information.gib(mem_gib), |
| 27 | + disk_free: Information.gib(disk_gib), |
| 28 | + cpu_load: 0.0, |
| 29 | + cpu_capacity: cpu_cap, |
| 30 | + cpu_max_load: 1.0, |
| 31 | + disk_bw_load: Bandwidth.zero(), |
| 32 | + disk_bw_ceiling: Bandwidth.gibps(10), |
| 33 | + net_bw_load: Bandwidth.zero(), |
| 34 | + net_bw_ceiling: Bandwidth.gibps(10), |
| 35 | + layers: [] |
| 36 | + } |
| 37 | + end |
| 38 | + end |
| 39 | + |
| 40 | + # A spec whose demand sits within every ceiling of `st`. |
| 41 | + defp fitting_spec(st) do |
| 42 | + gen all( |
| 43 | + mem_gib <- integer(0..Information.as_gib(st.mem_free)), |
| 44 | + disk_gib <- integer(0..Information.as_gib(st.disk_free)), |
| 45 | + vcpus <- integer(0..st.cpu_capacity) |
| 46 | + ) do |
| 47 | + %Spec{ |
| 48 | + vcpus: vcpus, |
| 49 | + mem: Information.gib(mem_gib), |
| 50 | + disk: Information.gib(disk_gib), |
| 51 | + disk_bw: Bandwidth.zero(), |
| 52 | + net_bw: Bandwidth.zero() |
| 53 | + } |
| 54 | + end |
| 55 | + end |
| 56 | + |
| 57 | + property "a spec within every ceiling always fits" do |
| 58 | + check all(st <- state(), spec <- fitting_spec(st)) do |
| 59 | + assert NodeState.fits?(st, spec) |
| 60 | + end |
| 61 | + end |
| 62 | + |
| 63 | + property "exceeding free memory always fails" do |
| 64 | + check all(st <- state(), over <- integer(1..1000)) do |
| 65 | + spec = %Spec{ |
| 66 | + vcpus: 0, |
| 67 | + mem: st.mem_free + Information.gib(over), |
| 68 | + disk: Information.zero(), |
| 69 | + disk_bw: Bandwidth.zero(), |
| 70 | + net_bw: Bandwidth.zero() |
| 71 | + } |
| 72 | + |
| 73 | + refute NodeState.fits?(st, spec) |
| 74 | + end |
| 75 | + end |
| 76 | + |
| 77 | + property "a fitting spec still fits when the node gains headroom" do |
| 78 | + check all(st <- state(), spec <- fitting_spec(st), extra <- integer(0..100)) do |
| 79 | + roomier = %{ |
| 80 | + st |
| 81 | + | mem_free: st.mem_free + Information.gib(extra), |
| 82 | + disk_free: st.disk_free + Information.gib(extra) |
| 83 | + } |
| 84 | + |
| 85 | + # Precondition the law on the spec actually fitting `st`. |
| 86 | + if NodeState.fits?(st, spec), do: assert(NodeState.fits?(roomier, spec)) |
| 87 | + end |
| 88 | + end |
| 89 | +end |
0 commit comments