albatross/src/vmm_vmmd.ml

330 lines
12 KiB
OCaml

(* (c) 2017, 2018 Hannes Mehnert, all rights reserved *)
open Astring
open Vmm_core
open Rresult
open R.Infix
type 'a t = {
console_counter : int64 ;
stats_counter : int64 ;
log_counter : int64 ;
resources : Vmm_resources.t ;
waiters : 'a String.Map.t ;
}
let in_shutdown = ref false
let killall t =
match List.map snd (Vmm_trie.all t.resources.Vmm_resources.unikernels) with
| [] -> false
| vms -> in_shutdown := true ; List.iter Vmm_unix.destroy vms ; true
let remove_resources t name =
let resources = match Vmm_resources.remove_vm t.resources name with
| Error (`Msg e) ->
Logs.warn (fun m -> m "%s while removing vm %a from resources" e Name.pp name) ;
t.resources
| Ok resources -> resources
in
{ t with resources }
let dump_unikernels t =
let unikernels = Vmm_trie.all t.resources.Vmm_resources.unikernels in
let trie = List.fold_left (fun t (name, unik) ->
fst @@ Vmm_trie.insert name unik.Unikernel.config t)
Vmm_trie.empty unikernels
in
let data = Vmm_asn.unikernels_to_cstruct trie in
match Vmm_unix.dump data with
| Error (`Msg msg) -> Logs.err (fun m -> m "failed to dump unikernels: %s" msg)
| Ok () -> Logs.info (fun m -> m "dumped current state")
let waiter t id =
let t = remove_resources t id in
let name = Name.to_string id in
if not !in_shutdown then dump_unikernels t ;
match String.Map.find name t.waiters with
| None -> t, None
| Some waiter ->
let waiters = String.Map.remove name t.waiters in
{ t with waiters }, Some waiter
let register t id create =
let name = Name.to_string id in
let task, waiter = create () in
{ t with waiters = String.Map.add name waiter t.waiters }, task
let register_restart t id create =
let name = Name.to_string id in
match String.Map.find name t.waiters with
| Some _ -> Logs.err (fun m -> m "restart attempted to overwrite waiter"); None
| _ -> Some (register t id create)
let init () =
let t = {
console_counter = 1L ;
stats_counter = 1L ;
log_counter = 1L ;
resources = Vmm_resources.empty ;
waiters = String.Map.empty ;
} in
match Vmm_unix.find_block_devices () with
| Error (`Msg msg) ->
Logs.warn (fun m -> m "couldn't find block devices %s" msg) ;
t
| Ok devs ->
let resources =
List.fold_left (fun r (id, size) ->
match Vmm_resources.insert_block r id size with
| Error (`Msg msg) ->
Logs.err (fun m -> m "couldn't insert block device %a (%dMB): %s" Name.pp id size msg) ;
r
| Ok r -> r)
t.resources devs
in
{ t with resources }
type 'a create =
Vmm_commands.wire *
('a t -> ('a t * Vmm_commands.wire * Vmm_commands.wire * Vmm_commands.res * Name.t * Unikernel.t, [ `Msg of string ]) result) *
(unit -> Vmm_commands.res)
let log t name event =
let data = (Ptime_clock.now (), event) in
let header = Vmm_commands.header ~sequence:t.log_counter name in
let log_counter = Int64.succ t.log_counter in
Logs.debug (fun m -> m "log %a" Log.pp data) ;
({ t with log_counter }, (header, `Data (`Log_data data)))
let restore_unikernels () =
match Vmm_unix.restore () with
| Error `NoFile ->
Logs.warn (fun m -> m "no state dump found, starting with no unikernels") ;
Ok Vmm_trie.empty
| Error (`Msg msg) -> Error (`Msg ("while reading state: " ^ msg))
| Ok data ->
match Vmm_asn.unikernels_of_cstruct data with
| Error (`Msg msg) -> Error (`Msg ("couldn't parse state: " ^ msg))
| Ok unikernels ->
Logs.info (fun m -> m "restored %d unikernels" (List.length (Vmm_trie.all unikernels))) ;
Ok unikernels
let setup_stats t name vm =
let stat_out =
let name = match Vmm_unix.vm_device vm with
| Error _ -> ""
| Ok name -> name
and ifs = Unikernel.(List.combine (List.map fst vm.config.bridges) vm.taps)
in
`Stats_add (name, vm.Unikernel.pid, ifs)
in
let header = Vmm_commands.header ~sequence:t.stats_counter name in
let t = { t with stats_counter = Int64.succ t.stats_counter } in
t, (header, `Command (`Stats_cmd stat_out))
let remove_stats t name =
let header = Vmm_commands.header ~sequence:t.stats_counter name in
let t = { t with stats_counter = Int64.succ t.stats_counter } in
(t, (header, `Command (`Stats_cmd `Stats_remove)))
let handle_create t name vm_config =
(match Vmm_resources.find_vm t.resources name with
| Some _ -> Error (`Msg "VM with same name is already running")
| None -> Ok ()) >>= fun () ->
Logs.debug (fun m -> m "now checking resource policies") ;
Vmm_resources.check_vm t.resources name vm_config >>= fun () ->
(* prepare VM: save VM image to disk, create fifo, ... *)
Vmm_unix.prepare name vm_config >>= fun taps ->
Logs.debug (fun m -> m "prepared vm with taps %a"
Fmt.(list ~sep:(unit ",@ ") (pair ~sep:(unit " -> ") string string))
taps) ;
let cons_out =
let header = Vmm_commands.header ~sequence:t.console_counter name in
(header, `Command (`Console_cmd `Console_add))
in
let success t =
(* actually execute the vm:
- check for safety that executing it would not exceed any resources
- execute it
- update resources
--> if either the first or second fails, then the fail continuation
below needs to be called *)
Vmm_resources.check_vm t.resources name vm_config >>= fun () ->
let block_devices =
List.map (fun d -> d, Name.block_name name d)
vm_config.Unikernel.block_devices
in
Vmm_unix.exec name vm_config taps block_devices >>| fun vm ->
Logs.debug (fun m -> m "exec()ed vm") ;
let resources = Vmm_resources.insert_vm t.resources name vm in
let t = { t with resources } in
dump_unikernels t ;
let t, log_out =
let start =
`Unikernel_start (name, vm.Unikernel.pid, taps, block_devices)
in
log t name start
in
let t, stat_out = setup_stats t name vm in
(t, stat_out, log_out, `Success (`String "created VM"), name, vm)
and fail () =
match Vmm_unix.free_system_resources name (List.map snd taps) with
| Ok () -> `Failure "could not create VM: console failed"
| Error (`Msg msg) ->
let m = "could not create VM: console failed, and also " ^ msg ^ " while cleaning resources" in
`Failure m
in
Ok ({ t with console_counter = Int64.succ t.console_counter },
(cons_out, success, fail))
let handle_shutdown t name vm r =
(match Vmm_unix.free_system_resources name vm.Unikernel.taps with
| Ok () -> ()
| Error (`Msg e) ->
Logs.err (fun m -> m "%s while shutdown vm %a" e Unikernel.pp vm));
let t, log_out = log t name (`Unikernel_stop (name, vm.Unikernel.pid, r)) in
let t, stat_out = remove_stats t name in
(t, stat_out, log_out)
let handle_policy_cmd t id = function
| `Policy_remove ->
Logs.debug (fun m -> m "remove policy %a" Name.pp id) ;
Vmm_resources.remove_policy t.resources id >>= fun resources ->
Ok ({ t with resources }, `End (`Success (`String "removed policy")))
| `Policy_add policy ->
Logs.debug (fun m -> m "insert policy %a" Name.pp id) ;
let same_policy = match Vmm_resources.find_policy t.resources id with
| None -> false
| Some p' -> Policy.equal policy p'
in
if same_policy then
Ok (t, `Loop (`Success (`String "no modification of policy")))
else
Vmm_resources.insert_policy t.resources id policy >>= fun resources ->
Ok ({ t with resources }, `Loop (`Success (`String "added policy")))
| `Policy_info ->
Logs.debug (fun m -> m "policy %a" Name.pp id) ;
let policies =
Vmm_trie.fold id t.resources.Vmm_resources.policies
(fun prefix policy policies-> (prefix, policy) :: policies)
[]
in
match policies with
| [] ->
Logs.debug (fun m -> m "policies: couldn't find %a" Name.pp id) ;
Error (`Msg "policy: not found")
| _ ->
Ok (t, `End (`Success (`Policies policies)))
let handle_unikernel_cmd t id = function
| `Unikernel_info ->
Logs.debug (fun m -> m "info %a" Name.pp id) ;
let vms =
Vmm_trie.fold id t.resources.Vmm_resources.unikernels
(fun id vm vms ->
let cfg = { vm.Unikernel.config with image = Cstruct.empty } in
(id, cfg) :: vms)
[]
in
begin match vms with
| [] ->
Logs.debug (fun m -> m "info: couldn't find %a" Name.pp id) ;
Error (`Msg "info: no unikernel found")
| _ ->
Ok (t, `End (`Success (`Unikernels vms)))
end
| `Unikernel_get ->
Logs.debug (fun m -> m "get %a" Name.pp id) ;
begin match Vmm_trie.find id t.resources.Vmm_resources.unikernels with
| None -> Error (`Msg "get: no unikernel found")
| Some u ->
Ok (t, `End (`Success (`Unikernels [ (id, u.Unikernel.config) ])))
end
| `Unikernel_create vm_config -> Ok (t, `Create (id, vm_config))
| `Unikernel_force_create vm_config ->
begin
let resources =
match Vmm_resources.remove_vm t.resources id with
| Error _ -> t.resources | Ok r -> r
in
Vmm_resources.check_vm resources id vm_config >>= fun () ->
match Vmm_resources.find_vm t.resources id with
| None -> Ok (t, `Create (id, vm_config))
| Some vm ->
(match Vmm_unix.destroy vm with
| exception Unix.Unix_error _ -> ()
| () -> ());
Ok (t, `Wait_and_create (id, (id, vm_config)))
end
| `Unikernel_destroy ->
match Vmm_resources.find_vm t.resources id with
| None -> Error (`Msg "destroy: not found")
| Some vm ->
let answer =
try
Vmm_unix.destroy vm ; "destroyed unikernel"
with
Unix.Unix_error _ -> "kill failed"
in
let s ex =
let data = Fmt.strf "%a %s %a" Name.pp id answer pp_process_exit ex in
`Success (`String data)
in
Ok (t, `Wait (id, s))
let handle_block_cmd t id = function
| `Block_remove ->
Logs.debug (fun m -> m "removing block %a" Name.pp id) ;
begin match Vmm_resources.find_block t.resources id with
| None -> Error (`Msg "remove block: not found")
| Some (_, true) -> Error (`Msg "remove block: is in use")
| Some (_, false) ->
Vmm_unix.destroy_block id >>= fun () ->
Vmm_resources.remove_block t.resources id >>= fun resources ->
Ok ({ t with resources }, `End (`Success (`String "removed block")))
end
| `Block_add size ->
begin
Logs.debug (fun m -> m "insert block %a: %dMB" Name.pp id size) ;
match Vmm_resources.find_block t.resources id with
| Some _ -> Error (`Msg "block device with same name already exists")
| None ->
Vmm_resources.check_block t.resources id size >>= fun () ->
Vmm_unix.create_block id size >>= fun () ->
Vmm_resources.insert_block t.resources id size >>= fun resources ->
Ok ({ t with resources }, `Loop (`Success (`String "added block device")))
end
| `Block_info ->
Logs.debug (fun m -> m "block %a" Name.pp id) ;
let blocks =
Vmm_trie.fold id t.resources.Vmm_resources.block_devices
(fun prefix (size, active) blocks -> (prefix, size, active) :: blocks)
[]
in
match blocks with
| [] ->
Logs.debug (fun m -> m "block: couldn't find %a" Name.pp id) ;
Error (`Msg "block: not found")
| _ ->
Ok (t, `End (`Success (`Block_devices blocks)))
let handle_command t (header, payload) =
let msg_to_err = function
| Ok x -> Ok x
| Error (`Msg msg) ->
Logs.err (fun m -> m "error while processing command: %s" msg) ;
Error (`Failure msg)
and id = header.Vmm_commands.name
in
msg_to_err (
match payload with
| `Command (`Policy_cmd pc) -> handle_policy_cmd t id pc
| `Command (`Unikernel_cmd vc) -> handle_unikernel_cmd t id vc
| `Command (`Block_cmd bc) -> handle_block_cmd t id bc
| _ ->
Logs.err (fun m -> m "ignoring %a" Vmm_commands.pp_wire (header, payload)) ;
Error (`Msg "unknown command"))