albatross/src/vmm_resources.ml

289 lines
9.4 KiB
OCaml

(* (c) 2017, 2018 Hannes Mehnert, all rights reserved *)
open Astring
open Rresult.R.Infix
open Vmm_core
type t = {
policies : Policy.t Vmm_trie.t ;
block_devices : (int * bool) Vmm_trie.t ;
unikernels : Unikernel.t Vmm_trie.t ;
}
let pp ppf t =
Vmm_trie.fold Name.root t.policies
(fun id p () ->
Fmt.pf ppf "policy %a: %a@." Name.pp id Policy.pp p) () ;
Vmm_trie.fold Name.root t.block_devices
(fun id (size, used) () ->
Fmt.pf ppf "block device %a: %dMB (used %B)@." Name.pp id size used) () ;
Vmm_trie.fold Name.root t.unikernels
(fun id vm () ->
Fmt.pf ppf "vm %a: %a@." Name.pp id Unikernel.pp_config vm.Unikernel.config) ()
let empty = {
policies = Vmm_trie.empty ;
block_devices = Vmm_trie.empty ;
unikernels = Vmm_trie.empty
}
let policy_metrics =
let open Metrics in
let doc = "VMM resource policies" in
let data policy =
Data.v [
uint "maximum unikernels" policy.Policy.vms ;
uint "maximum memory" policy.Policy.memory ;
uint "maximum block" (match policy.Policy.block with None -> 0 | Some x -> x)
]
in
let tag = Tags.string "domain" in
Src.v ~doc ~tags:Tags.[tag] ~data "vmm-policies"
let no_policy = Policy.{ vms = 0 ; cpuids = IS.empty ; memory = 0 ; block = None ; bridges = Astring.String.Set.empty }
(* we should confirm the following invariant: Vm or Block have no siblings *)
let block_usage t name =
Vmm_trie.fold name t.block_devices
(fun _ (size, act) (active, inactive) ->
if act then active + size, inactive else active, inactive + size)
(0, 0)
let total_block_usage t name =
let act, inact = block_usage t name in
act + inact
let vm_usage t name =
Vmm_trie.fold name t.unikernels
(fun _ vm (vms, memory) -> (succ vms, memory + vm.Unikernel.config.Unikernel.memory))
(0, 0)
let unikernel_metrics =
let open Metrics in
let doc = "VMM unikernels" in
let data (t, name) =
let vms, memory = vm_usage t name
and act, inact = block_usage t name
in
Data.v [
uint "attached used block" act ;
uint "unattached used block" inact ;
uint "total used block" (act + inact) ;
uint "running unikernels" vms ;
uint "used memory" memory
]
in
let tag = Tags.string "domain" in
Src.v ~doc ~tags:Tags.[tag] ~data "vmm-unikernels"
let rec report_vms t name =
let name' = Name.drop name in
let str = Name.to_string name' in
Metrics.add unikernel_metrics (fun x -> x str) (fun d -> d (t, name'));
if Name.is_root name' then () else report_vms t name'
let find_vm t name = Vmm_trie.find name t.unikernels
let find_policy t name = Vmm_trie.find name t.policies
let find_block t name = Vmm_trie.find name t.block_devices
let set_block_usage t name active =
match Vmm_trie.find name t with
| None -> invalid_arg ("block device " ^ Name.to_string name ^ " not in trie")
| Some (size, curr) ->
if curr = active
then invalid_arg ("block device " ^ Name.to_string name ^ " already in state " ^ (if curr then "active" else "inactive"))
else fst (Vmm_trie.insert name (size, active) t)
let use_blocks t name vm active =
match vm.Unikernel.config.Unikernel.block_devices with
| [] -> t
| blocks ->
let block_names = List.map (Name.block_name name) blocks in
List.fold_left (fun t' n -> set_block_usage t' n active) t block_names
let remove_vm t name = match find_vm t name with
| None -> Error (`Msg "unknown vm")
| Some vm ->
let block_devices = use_blocks t.block_devices name vm false in
let unikernels = Vmm_trie.remove name t.unikernels in
let t' = { t with block_devices ; unikernels } in
report_vms t' name;
Ok t'
let remove_policy t name = match find_policy t name with
| None -> Error (`Msg "unknown policy")
| Some _ ->
let policies = Vmm_trie.remove name t.policies in
Metrics.add policy_metrics (fun x -> x (Name.to_string name)) (fun d -> d no_policy);
Ok { t with policies }
let remove_block t name = match find_block t name with
| None -> Error (`Msg "unknown block")
| Some (_, active) ->
if active then
Error (`Msg "block device in use")
else
let block_devices = Vmm_trie.remove name t.block_devices in
let t' = { t with block_devices } in
report_vms t' name;
Ok t'
let bridge_allowed set s = String.Set.mem s set
let check_policy (p : Policy.t) (running_vms, used_memory) (vm : Unikernel.config) =
if succ running_vms > p.Policy.vms then
Error (`Msg "maximum amount of unikernels reached")
else if vm.Unikernel.memory > p.Policy.memory - used_memory then
Error (`Msg "maximum allowed memory reached")
else if not (IS.mem vm.Unikernel.cpuid p.Policy.cpuids) then
Error (`Msg "CPUid is not allowed by policy")
else if not (List.for_all (bridge_allowed p.Policy.bridges) (Unikernel.bridges vm)) then
Error (`Msg "network not allowed by policy")
else Ok ()
let check_vm t name vm =
let policy_ok =
let dom = Name.domain name in
match find_policy t dom with
| None -> Ok ()
| Some p ->
let used = vm_usage t dom in
check_policy p used vm
and block_ok =
List.fold_left (fun r block ->
r >>= fun () ->
let block_name = Name.block_name name block in
match find_block t block_name with
| None -> Error (`Msg "block device not found")
| Some (_, active) ->
if active then
Error (`Msg "block device already in use")
else
Ok ())
(Ok ()) vm.block_devices
and vm_ok = match find_vm t name with
| None -> Ok ()
| Some _ -> Error (`Msg "vm with same name already exists")
in
policy_ok >>= fun () ->
block_ok >>= fun () ->
vm_ok
let insert_vm t name vm =
let unikernels, old = Vmm_trie.insert name vm t.unikernels in
(match old with None -> () | Some _ -> invalid_arg ("unikernel " ^ Name.to_string name ^ " already exists in trie")) ;
let block_devices = use_blocks t.block_devices name vm true in
let t' = { t with unikernels ; block_devices } in
report_vms t' name;
t'
let check_block t name size =
let block_ok = match find_block t name with
| Some _ -> Error (`Msg "block device with same name already exists")
| None -> Ok ()
and policy_ok =
let dom = Name.domain name in
match find_policy t dom with
| None -> Ok ()
| Some p ->
let used = total_block_usage t dom in
match p.Policy.block with
| None -> Error (`Msg "no block devices are allowed by policy")
| Some limit ->
if size <= limit - used then
Ok ()
else
Error (`Msg "block device policy limit reached")
in
block_ok >>= fun () ->
policy_ok
let insert_block t name size =
check_block t name size >>= fun () ->
let block_devices = fst (Vmm_trie.insert name (size, false) t.block_devices) in
let t' = { t with block_devices } in
report_vms t' name;
Ok t'
let sub_policy ~super ~sub =
let sub_block sub super =
match super, sub with
| None, None -> true
| Some _, None -> true
| Some x, Some y -> x >= y
| None, Some _ -> false
in
if super.Policy.vms < sub.Policy.vms then
Error (`Msg "policy above allows fewer unikernels")
else if super.Policy.memory < sub.Policy.memory then
Error (`Msg "policy above allows fewer memory")
else if not (IS.subset sub.Policy.cpuids super.Policy.cpuids) then
Error (`Msg "policy above allows fewer cpuids")
else if not (String.Set.subset sub.Policy.bridges super.Policy.bridges) then
Error (`Msg "policy above allows fewer bridges")
else if not (sub_block sub.Policy.block super.Policy.block) then
Error (`Msg "policy above allows fewer block storage")
else
Ok ()
let check_policies_above t name sub =
let rec go prefix =
if Name.is_root prefix then
Ok ()
else
match find_policy t prefix with
| None -> go (Name.domain prefix)
| Some super ->
sub_policy ~super ~sub >>= fun () ->
go (Name.domain prefix)
in
go (Name.domain name)
let check_policies_below t curname super =
Vmm_trie.fold curname t.policies (fun name policy res ->
res >>= fun () ->
if Name.equal curname name then
res
else
sub_policy ~super ~sub:policy)
(Ok ())
let check_vms t name p =
let (vms, used_memory) = vm_usage t name
and block = total_block_usage t name
in
let bridges, cpuids =
Vmm_trie.fold name t.unikernels
(fun _ vm (bridges, cpuids) ->
let config = vm.Unikernel.config in
(String.Set.(union (of_list (Unikernel.bridges config)) bridges),
IS.add config.Unikernel.cpuid cpuids))
(String.Set.empty, IS.empty)
in
let policy_block = match p.Policy.block with None -> 0 | Some x -> x in
if not (IS.subset cpuids p.Policy.cpuids) then
Error (`Msg "used CPUid is not allowed by policy")
else if not (String.Set.subset bridges p.Policy.bridges) then
Error (`Msg "used network not allowed by policy")
else if vms > p.Policy.vms then
Error (`Msg "policy would not allow amount of running unikernels")
else if used_memory > p.Policy.memory then
Error (`Msg "policy would not allow used memory")
else if block > policy_block then
Error (`Msg "policy would not allow used block storage")
else
Ok ()
let insert_policy t name p =
check_policies_above t name p >>= fun () ->
check_policies_below t name p >>= fun () ->
check_vms t name p >>= fun () ->
let policies = fst (Vmm_trie.insert name p t.policies) in
Metrics.add policy_metrics (fun x -> x (Name.to_string name)) (fun d -> d p);
Ok { t with policies }