vmmd: actually, first check resources, then exec VM, then insert VM

in case the insertion fails, raise Invalid_argument

this leads to more sane failure behaviour, and also cleans up resources in case
 vmm_resources.insert_vm fails (or cpuset/open of the fifo, create_process)
This commit is contained in:
Hannes Mehnert 2019-01-27 16:46:49 +01:00
parent 131dec0cdd
commit a9c32d7801
5 changed files with 32 additions and 25 deletions

View file

@ -31,8 +31,9 @@ let create stat_out log_out cons_out data_out cons succ_cont fail_cont =
data_out data
| Ok () -> match succ_cont !state with
| Error (`Msg msg) ->
Logs.err (fun m -> m "create continuation failed %s" msg) ;
Lwt.return_unit
Logs.err (fun m -> m "create (exec) failed %s" msg) ;
let data = fail_cont () in
data_out data
| Ok (state', stat, log, data, name, vm) ->
state := state' ;
s := { !s with vm_created = succ !s.vm_created } ;

View file

@ -51,16 +51,15 @@ 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 -> Error (`Msg "unknown block device")
| None -> invalid_arg ("block device " ^ Name.to_string name ^ " not in trie")
| Some (size, curr) ->
if curr = active then
Error (`Msg "failed because the requested block usage was already set")
else
Ok (fst (Vmm_trie.insert name (size, active) t))
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 maybe_use_block t name vm active =
let use_block t name vm active =
match vm.Unikernel.config.Unikernel.block_device with
| None -> Ok t
| None -> t
| Some block ->
let block_name = Name.block_name name block in
set_block_usage t block_name active
@ -68,9 +67,9 @@ let maybe_use_block t name vm active =
let remove_vm t name = match find_vm t name with
| None -> Error (`Msg "unknown vm")
| Some vm ->
maybe_use_block t.block_devices name vm false >>| fun block_devices ->
let block_devices = use_block t.block_devices name vm false in
let unikernels = Vmm_trie.remove name t.unikernels in
{ t with block_devices ; unikernels }
Ok { t with block_devices ; unikernels }
let remove_policy t name = match find_policy t name with
| None -> Error (`Msg "unknown policy")
@ -126,12 +125,10 @@ let check_vm t name vm =
vm_ok
let insert_vm t name vm =
check_vm t name vm.Unikernel.config >>= fun () ->
match Vmm_trie.insert name vm t.unikernels with
| unikernels, None ->
maybe_use_block t.block_devices name vm true >>| fun block_devices ->
{ t with unikernels ; block_devices }
| _, Some _ -> Error (`Msg "vm already exists")
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_block t.block_devices name vm true in
{ t with unikernels ; block_devices }
let check_block t name size =
let block_ok = match find_block t name with

View file

@ -37,9 +37,12 @@ val find_block : t -> Name.t -> (int * bool) option
allowed under the current policies. *)
val check_vm : t -> Name.t -> Unikernel.config -> (unit, [> `Msg of string ]) result
(** [insert_vm t Name.t vm] inserts [vm] under [Name.t] in [t], and returns the new [t] or
an error. *)
val insert_vm : t -> Name.t -> Unikernel.t -> (t, [> `Msg of string]) result
(** [insert_vm t Name.t vm] inserts [vm] under [Name.t] in [t], and returns the
new [t]. The caller has to ensure (using {!check_vm}) that a VM with the
same name does not yet exist, and the block device is not in use.
@raise Invalid_argument if block device is already in use, or VM already
exists. *)
val insert_vm : t -> Name.t -> Unikernel.t -> t
(** [insert_policy t Name.t policy] inserts [policy] under [Name.t] in [t], and returns
the new [t] or an error. *)

View file

@ -210,7 +210,7 @@ let exec name config taps block =
Logs.debug (fun m -> m "created process %d: %a" pid Bos.Cmd.pp cmd) ;
(* we gave a copy (well, two copies) of that file descriptor to the solo5
process and don't really need it here anymore... *)
close stdout ;
close_no_err stdout ;
Ok Unikernel.{ config ; cmd ; pid ; taps }
with
Unix.Unix_error (e, _, _) ->

View file

@ -132,19 +132,25 @@ let handle_create t hdr name vm_config =
(header, `Command (`Console_cmd `Console_add))
in
let success t =
(* actually execute the vm *)
(* 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 *)
let block_device = match vm_config.Unikernel.block_device with
| None -> None
| Some block -> Some (Name.block_name name block)
in
Vmm_unix.exec name vm_config taps block_device >>= fun vm ->
Vmm_resources.check_vm t.resources name vm_config >>= fun () ->
Vmm_unix.exec name vm_config taps block_device >>| fun vm ->
Logs.debug (fun m -> m "exec()ed vm") ;
Vmm_resources.insert_vm t.resources name vm >>= fun resources ->
let resources = Vmm_resources.insert_vm t.resources name vm in
let t = { t with resources } in
dump_unikernels t ;
let t, log_out = log t name (`Unikernel_start (name, vm.Unikernel.pid, vm.Unikernel.taps, None)) in
let t, stat_out = setup_stats t name vm in
Ok (t, stat_out, log_out, (hdr, `Success (`String "created VM")), name, vm)
(t, stat_out, log_out, (hdr, `Success (`String "created VM")), name, vm)
and fail () =
match Vmm_unix.free_resources name taps with
| Ok () -> (hdr, `Failure "could not create VM: console failed")