Skip to content

Commit e990679

Browse files
committed
xapi_vm_snapshot: change VM.revert to use VDI.revert
The code now tries to call VDI.revert on all the disk VDIs, and if that fails, it falls back to the original revert method that destroys VM disks, clones snapshot disks and then fixes up metadata. This means that the successfully reverted disks must be excluded from the original method. This is done by introducing sets of VDIs and VBDs and using set logic on them. The CD disks and the suspend VDI are treated like before: destroy + clone. The code is more convoluted that I would have liked because of existing clone infrastructure mixes VBDs with VDIs, so they need to be converted back and forth to be able to run the previous method correctly, especially for updating the snapshot_of field of the cloned snapshot disks. Quicktests encodes the difference in behaviour from the original method to the native one: now VDI UUIDs are not changed when reverting. Signed-off-by: Pau Ruiz Safont <pau.safont@vates.tech> (cherry picked from commit d1ae2a7)
1 parent d24549a commit e990679

6 files changed

Lines changed: 179 additions & 53 deletions

File tree

ocaml/idl/datamodel_common.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ open Datamodel_roles
1010
to leave a gap for potential hotfixes needing to increment the schema version.*)
1111
let schema_major_vsn = 5
1212

13-
let schema_minor_vsn = 905
13+
let schema_minor_vsn = 906
1414

1515
(* Historical schema versions just in case this is useful later *)
1616
let rio_schema_major_vsn = 5

ocaml/idl/schematest.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex
33
(* BEWARE: if this changes, check that schema has been bumped accordingly in
44
ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *)
55

6-
let last_known_schema_hash = "f45d29452df691507ce8fc9f539649f1"
6+
let last_known_schema_hash = "247a7b080219583e50e3e1a3c3c85b08"
77

88
let current_schema_hash : string =
99
let open Datamodel_types in

ocaml/quicktest/qt_filter.ml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
module Listext = Xapi_stdext_std.Listext.List
12
module A = Quicktest_args
23

34
type 'a test_case = string * Alcotest.speed_level * 'a
@@ -312,14 +313,13 @@ module SR = struct
312313
)
313314

314315
let allowed_operations ops =
315-
sr_filter (fun i ->
316-
Xapi_stdext_std.Listext.List.subset ops i.Qt.allowed_operations
317-
)
316+
sr_filter (fun i -> Listext.subset ops i.Qt.allowed_operations)
318317

319318
let has_capabilities caps =
320-
sr_filter (fun i ->
321-
Xapi_stdext_std.Listext.List.subset caps i.Qt.capabilities
322-
)
319+
sr_filter (fun i -> Listext.subset caps i.Qt.capabilities)
320+
321+
let unavailable_operations ops =
322+
sr_filter (fun i -> not (Listext.subset ops i.Qt.allowed_operations))
323323

324324
(* Helper to filter SRs of specific types *)
325325
let has_one_of_types types sr_info =

ocaml/quicktest/qt_filter.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ module SR : sig
6464

6565
val allowed_operations : API.storage_operations_set -> srs -> srs
6666

67+
val unavailable_operations : API.storage_operations_set -> srs -> srs
68+
6769
val has_capabilities : string list -> srs -> srs
6870

6971
val has_type : string -> srs -> srs

ocaml/quicktest/quicktest_vm_snapshot.ml

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,22 @@ let test_snapshot_ignore_vdi rpc session_id vm vdi vdi2 =
143143
(has_been_snapshot "1") ;
144144
check_vdi_snapshot_of rpc session_id vbds ~vdi "0"
145145

146-
let test_revert rpc session_id vm vdi vdi2 =
146+
let test_revert rpc session_id vm vdi vdi2 ~change =
147147
let snapshot = take_snapshot rpc session_id vm ~origin:__FUNCTION__ in
148148
Client.Client.VM.revert ~rpc ~session_id ~snapshot ;
149149

150150
let vbds = Client.Client.VM.get_VBDs ~rpc ~session_id ~self:vm in
151151
let vdi_after = get_vdi_with_user_device rpc session_id vbds "0" in
152152
let vdi_after2 = get_vdi_with_user_device rpc session_id vbds "1" in
153153

154-
(* Xapi forces VDI clones, the VDIs' IDs will always change *)
155-
check_vdis_different vdi vdi_after ;
156-
check_vdis_different vdi2 vdi_after2
154+
let check =
155+
if change then
156+
(* Xapi forces VDI clones, the VDIs' IDs will always change *)
157+
check_vdis_different
158+
else
159+
check_vdis_same
160+
in
161+
check vdi vdi_after ; check vdi2 vdi_after2
157162

158163
let test_revert_cds rpc session_id vm vdi vdi2 =
159164
let snapshot = take_snapshot rpc session_id vm ~origin:__FUNCTION__ in
@@ -178,13 +183,35 @@ let suite name with_setup tests sr_ops =
178183
|> sr SR.(all |> allowed_operations sr_ops)
179184
|> vm_template Qt.VM.Template.other
180185

186+
let suite_split_revert name with_setup =
187+
let open Qt_filter in
188+
let needed_ops = [`vdi_create] in
189+
let old_ops = [`vdi_clone] in
190+
let new_ops = [`vdi_revert] in
191+
let sr_candidates = SR.(all |> allowed_operations needed_ops) in
192+
let sr_native = sr_candidates |> SR.allowed_operations new_ops in
193+
let sr_clonables =
194+
sr_candidates
195+
|> SR.unavailable_operations new_ops
196+
|> SR.allowed_operations old_ops
197+
in
198+
let tests (filter_name, sr_filter) tests_f =
199+
let name = Printf.sprintf "%s (%s)" name filter_name in
200+
[(name, `Slow, a_test with_setup tests_f)]
201+
|> conn
202+
|> sr sr_filter
203+
|> vm_template Qt.VM.Template.other
204+
in
205+
tests ("with VDI.revert", sr_native) [test_revert ~change:false]
206+
@ tests ("with cloning method", sr_clonables) [test_revert ~change:true]
207+
181208
let tests () =
182209
List.concat
183210
[
184-
suite "VM snapshot tests" with_setup
211+
suite "VM snapshot" with_setup
185212
[test_snapshot; test_snapshot_ignore_vdi]
186213
[`vdi_create]
187-
; suite "VM revert tests" with_setup [test_revert] [`vdi_create; `vdi_clone]
188-
; suite "VM revert with CD tests" with_cd_setup [test_revert_cds]
214+
; suite_split_revert "VM revert" with_setup
215+
; suite "VM revert with CD" with_cd_setup [test_revert_cds]
189216
[`vdi_create; `vdi_clone]
190217
]

ocaml/xapi/xapi_vm_snapshot.ml

Lines changed: 135 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -211,61 +211,155 @@ let with_vdis_on_error ~vdis f = try f () with e -> Error (e, vdis)
211211

212212
let ( let@ ) f x = f x
213213

214-
(* Copy the VBDs and VIFs from a source VM to a dest VM and then delete the old
215-
disks. This operation destroys the data of the dest VM. *)
214+
(* Revert the VBDs of a VM to have the contents of the snapshot and copy the
215+
VIFs from a snapshot VM to the VM. This operation destroys the data of the
216+
dest VM. *)
217+
216218
type cloned = {
217219
disks: ([`VBD] Ref.t * API.ref_VDI * bool) list
218220
; cds: ([`VBD] Ref.t * API.ref_VDI * bool) list
219221
; suspend_VDI: [`VDI] Ref.t
220222
}
221223

224+
module VDISet = Set.Make (struct
225+
type t = [`VDI] Ref.t
226+
227+
let compare = Ref.compare
228+
end)
229+
230+
module VBDSet = Set.Make (struct
231+
type t = [`VBD] Ref.t
232+
233+
let compare = Ref.compare
234+
end)
235+
222236
let revert_vbds ~__context ~rpc ~session_id ~snapshot ~vm =
223-
let snap_VBDs = Db.VM.get_VBDs ~__context ~self:snapshot in
224-
let snap_VBDs_disk, snap_VBDs_CD =
225-
List.partition
226-
(fun vbd -> Db.VBD.get_type ~__context ~self:vbd = `Disk)
227-
snap_VBDs
228-
in
229-
let snap_disks =
230-
List.map (fun vbd -> Db.VBD.get_VDI ~__context ~self:vbd) snap_VBDs_disk
237+
let get_snapshot_of vdi = Db.VDI.get_snapshot_of ~__context ~self:vdi in
238+
239+
let disks_of_vbds vbds =
240+
vbds
241+
|> VBDSet.to_seq
242+
|> Seq.map (fun vbd -> Db.VBD.get_VDI ~__context ~self:vbd)
243+
|> VDISet.of_seq
231244
in
232-
let snap_disks_snapshot_of =
233-
List.map (fun vdi -> Db.VDI.get_snapshot_of ~__context ~self:vdi) snap_disks
245+
246+
let snap_VBDs_disk, snap_VBDs_CD =
247+
Db.VM.get_VBDs ~__context ~self:snapshot
248+
|> VBDSet.of_list
249+
|> VBDSet.partition (fun vbd -> Db.VBD.get_type ~__context ~self:vbd = `Disk)
234250
in
235251
let snap_suspend_VDI = Db.VM.get_suspend_VDI ~__context ~self:snapshot in
252+
let snap_disks_all = disks_of_vbds snap_VBDs_disk in
253+
let snap_disks_snapshot_of = VDISet.map get_snapshot_of snap_disks_all in
236254

237-
let vm_VBDs = Db.VM.get_VBDs ~__context ~self:vm in
238-
(* Filter VBDs to ensure that we don't read empty CDROMs *)
255+
let vm_VBDs_all = Db.VM.get_VBDs ~__context ~self:vm |> VBDSet.of_list in
239256
let vm_VBDs_disk =
240-
List.filter
257+
(* Filter VBDs to ensure that we don't read empty CDROMs *)
258+
VBDSet.filter
241259
(fun vbd -> Db.VBD.get_type ~__context ~self:vbd = `Disk)
242-
vm_VBDs
260+
vm_VBDs_all
261+
in
262+
let vm_disks_all = disks_of_vbds vm_VBDs_disk in
263+
264+
let vm_suspend_VDI =
265+
Db.VM.get_suspend_VDI ~__context ~self:vm |> VDISet.singleton
243266
in
244-
(* Filter out VM disks for which the snapshot does not have a corresponding
245-
disk - these disks will be left unattached after the revert is complete. *)
246-
let vm_disks =
247-
List.map (fun vbd -> Db.VBD.get_VDI ~__context ~self:vbd) vm_VBDs_disk
267+
268+
if VDISet.cardinal snap_disks_all <> 0 then
269+
debug "%s: trying to revert VDIs using VDI.revert" __FUNCTION__ ;
270+
271+
let snap_disks_reverted =
272+
VDISet.filter
273+
(fun snapshot ->
274+
try
275+
Client.VDI.revert ~rpc ~session_id ~snapshot ;
276+
true
277+
with _ -> false
278+
)
279+
snap_disks_all
248280
in
249-
let vm_disks_with_snapshot =
250-
List.filter (fun vdi -> List.mem vdi snap_disks_snapshot_of) vm_disks
281+
282+
let vm_disks_already_reverted =
283+
VDISet.map get_snapshot_of snap_disks_reverted
251284
in
252-
let vm_suspend_VDI = Db.VM.get_suspend_VDI ~__context ~self:vm in
253285

254-
debug "Cleaning up the old VBDs and VDIs to have more free space" ;
255-
List.iter (safe_destroy_vbd ~__context ~rpc ~session_id) vm_VBDs ;
256-
List.iter
286+
let vm_disks_to_be_destroyed =
287+
let ( --- ) = VDISet.diff in
288+
let ( +++ ) = VDISet.union in
289+
290+
(* Disks without snapshot are left unattached after the revert is complete. *)
291+
let vm_disks_without_snapshot = vm_disks_all --- snap_disks_snapshot_of in
292+
293+
vm_disks_all
294+
--- vm_disks_without_snapshot
295+
--- vm_disks_already_reverted
296+
+++ vm_suspend_VDI
297+
in
298+
299+
let filter_vbds_from_vdis vbds vdis =
300+
vbds
301+
|> VBDSet.filter (fun vbd ->
302+
VDISet.mem (Db.VBD.get_VDI ~__context ~self:vbd) vdis
303+
)
304+
in
305+
306+
let vm_vbds_to_be_destroyed =
307+
filter_vbds_from_vdis vm_VBDs_all vm_disks_to_be_destroyed
308+
in
309+
310+
let snap_VBDs_reverted =
311+
filter_vbds_from_vdis snap_VBDs_disk snap_disks_reverted
312+
in
313+
314+
(*
315+
snap VBDs (disk)
316+
- snap VBDs from reverted disks
317+
= snap VBDs to be cloned
318+
*)
319+
let snap_VBDs_to_be_cloned =
320+
let ( --- ) = VBDSet.diff in
321+
snap_VBDs_disk --- snap_VBDs_reverted
322+
in
323+
if
324+
VBDSet.cardinal vm_vbds_to_be_destroyed <> 0
325+
|| VDISet.cardinal vm_disks_to_be_destroyed <> 0
326+
then
327+
debug "%s: Cleaning up the old VBDs and VDIs to have more free space"
328+
__FUNCTION__ ;
329+
VBDSet.iter
330+
(safe_destroy_vbd ~__context ~rpc ~session_id)
331+
vm_vbds_to_be_destroyed ;
332+
VDISet.iter
257333
(safe_destroy_vdi ~__context ~rpc ~session_id)
258-
(vm_suspend_VDI :: vm_disks_with_snapshot) ;
334+
vm_disks_to_be_destroyed ;
259335
TaskHelper.set_progress ~__context 0.2 ;
260-
debug "Cloning the snapshotted disks" ;
336+
if VBDSet.cardinal snap_VBDs_to_be_cloned <> 0 then
337+
debug "%s: Cloning the snapshotted disks" __FUNCTION__ ;
338+
261339
let driver_params = Xapi_vm_clone.make_driver_params () in
262340
let cloned_disks =
263-
Xapi_vm_clone.safe_clone_disks rpc session_id Xapi_vm_clone.Disk_op_clone
264-
~__context snap_VBDs_disk driver_params
341+
(* the list of cloned VDIs maintains the order of the VBDs given, use this
342+
to correlate the VDI with the original VDI before the clone *)
343+
let snap_VBDs_to_be_cloned =
344+
snap_VBDs_to_be_cloned |> VBDSet.to_seq |> List.of_seq
345+
in
346+
let snap_disks_previous_snapshot_of =
347+
snap_VBDs_to_be_cloned
348+
|> List.map (fun vbd ->
349+
let vdi = Db.VBD.get_VDI ~__context ~self:vbd in
350+
Db.VDI.get_snapshot_of ~__context ~self:vdi
351+
)
352+
in
353+
354+
let cloned =
355+
Xapi_vm_clone.safe_clone_disks rpc session_id Xapi_vm_clone.Disk_op_clone
356+
~__context snap_VBDs_to_be_cloned driver_params
357+
in
358+
List.combine cloned snap_disks_previous_snapshot_of
265359
in
266360
let destroy_vdis_on_error =
267361
List.filter_map
268-
(fun (_, vdi, on_error_delete) ->
362+
(fun ((_, vdi, on_error_delete), _) ->
269363
if on_error_delete then
270364
Some vdi
271365
else
@@ -275,20 +369,22 @@ let revert_vbds ~__context ~rpc ~session_id ~snapshot ~vm =
275369
in
276370
let@ () = with_vdis_on_error ~vdis:destroy_vdis_on_error in
277371
let cloned_CDs =
372+
let snap_VBDs_CD = snap_VBDs_CD |> VBDSet.to_seq |> List.of_seq in
278373
Xapi_vm_clone.safe_clone_disks rpc session_id Xapi_vm_clone.Disk_op_clone
279374
~__context snap_VBDs_CD driver_params
280375
in
281376
TaskHelper.set_progress ~__context 0.5 ;
282-
debug "Updating the snapshot_of fields for relevant VDIs" ;
283-
List.iter2
284-
(fun snap_disk (_, cloned_disk, _) ->
377+
if cloned_disks <> [] then
378+
debug "%s: Updating the snapshot_of fields for relevant VDIs" __FUNCTION__ ;
379+
380+
List.iter
381+
(fun ((_, cloned_disk, _), snapshot_of) ->
285382
(* For each snapshot disk which was just cloned:
286383
1) Find the value of snapshot_of
287384
2) Find all snapshots with the same snapshot_of
288385
3) Update each of these snapshots so that their snapshot_of points
289386
to the new cloned disk. *)
290387
let open Xapi_database.Db_filter_types in
291-
let snapshot_of = Db.VDI.get_snapshot_of ~__context ~self:snap_disk in
292388
let all_snaps_in_tree =
293389
Db.VDI.get_refs_where ~__context
294390
~expr:(Eq (Field "snapshot_of", Literal (Ref.string_of snapshot_of)))
@@ -299,7 +395,7 @@ let revert_vbds ~__context ~rpc ~session_id ~snapshot ~vm =
299395
)
300396
all_snaps_in_tree
301397
)
302-
snap_disks cloned_disks ;
398+
cloned_disks ;
303399
debug "Cloning the suspend VDI if needed" ;
304400
let cloned_suspend_VDI =
305401
if snap_suspend_VDI = Ref.null then
@@ -310,12 +406,13 @@ let revert_vbds ~__context ~rpc ~session_id ~snapshot ~vm =
310406
in
311407
let destroy_vdis_on_error = cloned_suspend_VDI :: destroy_vdis_on_error in
312408
let@ () = with_vdis_on_error ~vdis:destroy_vdis_on_error in
409+
let vbds_to_copy = List.map fst cloned_disks @ cloned_CDs in
313410
TaskHelper.set_progress ~__context 0.6 ;
314-
debug "Copying the VBDs" ;
411+
if vbds_to_copy <> [] then debug "%s: Copying the VBDs" __FUNCTION__ ;
315412
let (_ : [`VBD] Ref.t list) =
316413
List.map
317414
(fun (vbd, vdi, _) -> Xapi_vbd_helpers.copy ~__context ~vm ~vdi vbd)
318-
(cloned_disks @ cloned_CDs)
415+
vbds_to_copy
319416
in
320417
debug "Update the suspend_VDI" ;
321418
Db.VM.set_suspend_VDI ~__context ~self:vm ~value:cloned_suspend_VDI ;

0 commit comments

Comments
 (0)