package albatross

  1. Overview
  2. Docs
Albatross - orchestrate and manage MirageOS unikernels with Solo5

Install

dune-project
 Dependency

Authors

Maintainers

Sources

albatross-2.6.0.tbz
sha256=95335cd203ba8f4b47a0fa2135ae2adc677c5a09a9e85bf729800eeb78f79de6
sha512=5661030612576a2941f57935cf9ceaf04b859e58cd6e70cc1372a9491b85ddaa17e3773c27df5c072fc166533f66ee0663fd52b2e1c4451f1e119dda26ebbac9

doc/src/albatross/vmm_resources.ml.html

Source file vmm_resources.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
(* (c) 2017, 2018 Hannes Mehnert, all rights reserved *)

open Vmm_core

let ( let* ) = Result.bind

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_path t.policies
    (fun id p () ->
       Fmt.pf ppf "policy %a: %a@." Name.pp id Policy.pp p) () ;
  Vmm_trie.fold Name.root_path t.block_devices
    (fun id (size, used) () ->
       Fmt.pf ppf "block device %a: %d MB (used %B)@." Name.pp id size used) () ;
  Vmm_trie.fold Name.root_path t.unikernels
    (fun id unikernel () ->
       Fmt.pf ppf "unikernel %a: %a@." Name.pp id Unikernel.pp_config unikernel.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 = "Albatross resource policies" in
  let data policy =
    Data.v [
      uint "maximum unikernels" policy.Policy.unikernels ;
      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 "albatross-policies"

let no_policy = Policy.{ unikernels = 0 ; cpuids = IS.empty ; memory = 0 ; block = None ; bridges = String_set.empty }

(* we should confirm the following invariant: Unikernel or Block have no siblings *)

let block_usage t path =
  Vmm_trie.fold path 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 path =
  let act, inact = block_usage t path in
  act + inact

let unikernel_usage t path =
  Vmm_trie.fold path t.unikernels
    (fun _ unikernel (unikernels, memory) ->
       (succ unikernels, memory + unikernel.Unikernel.config.Unikernel.memory))
    (0, 0)

let unikernel_metrics =
  let open Metrics in
  let doc = "Albatross unikernels" in
  let data (t, path) =
    let unikernels, memory = unikernel_usage t path
    and act, inact = block_usage t path
    in
    Data.v [
      uint "attached used block" act ;
      uint "unattached used block" inact ;
      uint "total used block" (act + inact) ;
      uint "running unikernels" unikernels ;
      uint "used memory" memory
    ]
  in
  let tag = Tags.string "domain" in
  Src.v ~doc ~tags:Tags.[tag] ~data "albatross-unikernels"

let report_unikernels t name =
  let rec doit path =
    let str =
      if Name.is_root_path path then ":" else Name.path_to_string path
    in
    Metrics.add unikernel_metrics (fun x -> x str) (fun d -> d (t, path));
    if Name.is_root_path path then () else doit (Name.parent_path path)
  in
  doit (Name.path name)

let find_unikernel t name = Vmm_trie.find name t.unikernels

let find_policy t path =
  Vmm_trie.find (Vmm_core.Name.create_of_path path) 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 unikernel active =
  match unikernel.Unikernel.config.Unikernel.block_devices with
  | [] -> t
  | blocks ->
    let block_names =
      List.map (fun (bd, dev, _sector_size) ->
          let bd = match dev with None -> bd | Some b -> b in
          Name.block_name name bd)
        blocks
    in
    List.fold_left (fun t' n -> set_block_usage t' n active) t block_names

let remove_unikernel t name = match find_unikernel t name with
  | None -> Error (`Msg "unknown unikernel")
  | Some unikernel ->
    let block_devices = use_blocks t.block_devices name unikernel false in
    let unikernels = Vmm_trie.remove name t.unikernels in
    let t' = { t with block_devices ; unikernels } in
    report_unikernels t' name;
    Ok t'

let remove_policy t path = match find_policy t path with
  | None -> Error (`Msg "unknown policy")
  | Some _ ->
    let policies =
      Vmm_trie.remove (Vmm_core.Name.create_of_path path) t.policies
    in
    Metrics.add policy_metrics
      (fun x -> x (Name.path_to_string path)) (fun d -> d no_policy);
    Ok { t with policies }

let remove_block t name =
  match find_block t name with
  | None -> Error (`Msg (Fmt.str "unknown block device %s" (Name.to_string name)))
  | Some (_, active) ->
    if active then
      Error (`Msg (Fmt.str "block device %s in use" (Name.to_string name)))
    else
      let block_devices = Vmm_trie.remove name t.block_devices in
      let t' = { t with block_devices } in
      report_unikernels t' name;
      Ok t'

let bridge_allowed set s = String_set.mem s set

let check_policy (p : Policy.t) (running_unikernels, used_memory) (unikernel : Unikernel.config) =
  if succ running_unikernels > p.Policy.unikernels then
    Error (`Msg (Fmt.str "maximum amount of unikernels (%d) reached" p.Policy.unikernels))
  else if unikernel.Unikernel.memory > p.Policy.memory - used_memory then
    Error (`Msg (Fmt.str
                   "maximum allowed memory (%d, used %d) would be exceeded (requesting %d)"
                   p.Policy.memory used_memory unikernel.Unikernel.memory))
  else if not (IS.mem unikernel.Unikernel.cpuid p.Policy.cpuids) then
    Error (`Msg (Fmt.str "CPUid %u is not allowed by policy" unikernel.Unikernel.cpuid))
  else
    match List.partition (bridge_allowed p.Policy.bridges) (Unikernel.bridges unikernel) with
    | _, [] -> Ok ()
    | _, disallowed ->
      Error (`Msg (Fmt.str "bridges %a not allowed by policy"
                     Fmt.(list ~sep:(any ", ") string) disallowed))

let check_unikernel t name unikernel =
  let policy_ok =
    let path = Name.path name in
    match find_policy t path with
    | None -> Ok ()
    | Some p ->
      let used = unikernel_usage t path in
      check_policy p used unikernel
  and block_ok =
    List.fold_left (fun r (block, dev, _sector_size) ->
        let* () = r in
        let bl = match dev with Some b -> b | None -> block in
        let block_name = Name.block_name name bl in
        match find_block t block_name with
        | None ->
          Error (`Msg (Fmt.str "block device %s not found" (Name.to_string block_name)))
        | Some (_, active) ->
          if active then
            Error (`Msg (Fmt.str "block device %s already in use" (Name.to_string block_name)))
          else
            Ok ())
      (Ok ()) unikernel.block_devices
  and unikernel_ok = match find_unikernel t name with
    | None -> Ok ()
    | Some _ -> Error (`Msg "unikernel with same name already exists")
  in
  let* () = policy_ok in
  let* () = block_ok in
  unikernel_ok

let insert_unikernel t name unikernel =
  let unikernels, old = Vmm_trie.insert name unikernel 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 unikernel true in
  let t' = { t with unikernels ; block_devices } in
  report_unikernels t' name;
  t'

let check_block t name size =
  let block_ok = match find_block t name with
    | Some _ ->
      Error (`Msg (Fmt.str "block device with name %a already exists" Name.pp name))
    | None -> Ok ()
  and policy_ok =
    let path = Name.path name in
    match find_policy t path with
    | None -> Ok ()
    | Some p ->
      let used = total_block_usage t path 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 (Fmt.str "block device policy limit of %d MB (used %d MB) would be exceeded by the request (%d MB)"
                         limit used size))
  in
  let* () = block_ok in
  policy_ok

let insert_block t name size =
  let* () = check_block t name size in
  let block_devices = fst (Vmm_trie.insert name (size, false) t.block_devices) in
  let t' = { t with block_devices } in
  report_unikernels t' name;
  Ok t'

let reserve_block t name size =
  let* () = check_block t name size in
  let block_devices = fst (Vmm_trie.insert name (size, true) t.block_devices) in
  Ok { t with block_devices }

let commit_block t name =
  match Vmm_trie.find name t.block_devices with
  | None -> Error (`Msg ("block device " ^ Name.to_string name ^ " not in trie"))
  | Some (size, curr) ->
    if not curr
    then Error (`Msg ("block device " ^ Name.to_string name ^ " already in state inactive"))
    else
      let block_devices = fst (Vmm_trie.insert name (size, false) t.block_devices) in
      Ok { t with block_devices }

let check_policies_above t path sub =
  let rec go prefix =
    if Name.is_root_path prefix then
      Ok ()
    else
      let* () =
        match find_policy t prefix with
        | None -> Ok ()
        | Some super -> Policy.is_smaller ~super ~sub
      in
      go (Name.parent_path prefix)
  in
  go (Name.parent_path path)

let check_policies_below t path super =
  Vmm_trie.fold path t.policies (fun name policy res ->
      let* () = res in
      if Name.is_root name then
        res
      else
        Policy.is_smaller ~super ~sub:policy)
    (Ok ())

let check_unikernels t path p =
  let (unikernels, used_memory) = unikernel_usage t path
  and block = total_block_usage t path
  in
  let bridges, cpuids =
    Vmm_trie.fold path t.unikernels
      (fun _ unikernel (bridges, cpuids) ->
         let config = unikernel.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 (Fmt.str "policy allows CPUids %a, which is not a superset of %a"
                   Fmt.(list ~sep:(any ", ") int) (IS.elements p.Policy.cpuids)
                   Fmt.(list ~sep:(any ", ") int) (IS.elements cpuids)))
  else if not (String_set.subset bridges p.Policy.bridges) then
    Error (`Msg (Fmt.str "policy allows bridges %a, which is not a superset of %a"
                   Fmt.(list ~sep:(any ", ") string) (String_set.elements p.Policy.bridges)
                   Fmt.(list ~sep:(any ", ") string) (String_set.elements bridges)))
  else if unikernels > p.Policy.unikernels then
    Error (`Msg (Fmt.str "unikernel would exceed running unikernel limit set by policy to %d, running %d"
                   p.Policy.unikernels unikernels))
  else if used_memory > p.Policy.memory then
    Error (`Msg (Fmt.str "unikernel would exceed running memory limit set by policy to %d MB, used %d MB"
                   p.Policy.memory used_memory))
  else if block > policy_block then
    Error (`Msg (Fmt.str "unikernel would exceed running block storage limit set by policy to %d MB, used %d MB"
                   policy_block block))
  else
    Ok ()

let insert_policy t path p =
  let* () = check_policies_above t path p in
  let* () = check_policies_below t path p in
  let* () = check_unikernels t path p in
  let policies =
    fst (Vmm_trie.insert (Vmm_core.Name.create_of_path path) p t.policies)
  in
  Metrics.add policy_metrics
    (fun x -> x (Name.path_to_string path)) (fun d -> d p);
  Ok { t with policies }