forked from xapi-project/xen-api
/
import.ml
1721 lines (1571 loc) · 71.1 KB
/
import.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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(*
* Copyright (C) 2006-2009 Citrix Systems Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; version 2.1 only. with the special
* exception on linking described in file LICENSE.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*)
(** HTTP handler for importing a VM from a stream.
* @group Import and Export
*)
module D=Debug.Make(struct let name="import" end)
open D
open Http
open Importexport
open Unixext
open Pervasiveext
open Threadext
open Fun
open Client
type import_failure =
| Some_checksums_failed
| Cannot_handle_chunked
| Failed_to_find_object of string
| Attached_disks_not_found
| Unexpected_file of string (* expected *) * string (* actual *)
exception IFailure of import_failure
open Xapi_vm_memory_constraints
open Vm_memory_constraints
type metadata_options = {
(* If true, don't create any database objects. *)
dry_run: bool;
(* If true, treat the import as if it is preparation for a live migration.
* This has the following consequences:
* - We must perform extra checks on the VM object - do we have enough memory? Are the CPU flags compatible? Is there an HA plan for it?
* - If the migration is a dry run we don't need to check for VDIs, since VDI.mirror will have created them during a real migration.
* - If the migration is for real, we will expect the VM export code on the source host to have mapped the VDI locations onto their
* mirrored counterparts which are present on this host. *)
live: bool;
(* An optional src VDI -> destination VDI rewrite list *)
vdi_map: (string * string) list;
}
type import_type =
(* Import the metadata of a VM whose disks already exist. *)
| Metadata_import of metadata_options
(* Import a VM and stream its disks into the specified SR. *)
| Full_import of API.ref_SR
(** Allows the import to be customised *)
type config =
{
(* Determines how to handle the import - see above. *)
import_type: import_type;
(* true if we want to restore as a perfect backup. Currently we preserve the
interface MAC addresses but we still regenerate UUIDs (because we lack the
internal APIs to keep them *)
full_restore: bool;
(* true if the user has provided '--force' *)
force: bool;
}
let is_live config =
match config.import_type with
| Metadata_import {live=live} -> live
| _ -> false
(** List of (datamodel classname * Reference in export * Reference in database) *)
type table = (string * string * string) list
(** Track the table of external reference -> internal reference and a list of cleanup
functions to delete all the objects we've created, in the event of error. *)
type state = {
mutable table: table;
mutable created_vms: table;
mutable cleanup: (Context.t -> (Rpc.call -> Rpc.response) -> API.ref_session -> unit) list;
export: obj list;
}
let initial_state export = { table = []; created_vms = []; cleanup = []; export = export }
let log_reraise msg f x =
try f x
with e ->
Backtrace.is_important e;
error "Import failed: %s" msg;
raise e
let lookup x (table: table) =
let id = Ref.string_of x in
try
let (_,_,r) = List.find (fun (_,i,_) -> i=id) table in
Ref.of_string r
with Not_found as e ->
Backtrace.reraise e (IFailure (Failed_to_find_object id))
let exists x (table: table) =
let id = Ref.string_of x in
List.filter (fun (_,i,_) -> i=id) table <> []
(* Using a reference string from the original export, find the XMLRPC snapshot
of the appropriate object. *)
let find_in_export x export =
try
let obj = List.find (fun obj -> obj.id = x) export in
obj.snapshot
with Not_found as e->
Backtrace.reraise e (IFailure (Failed_to_find_object x))
let choose_one = function
| x :: [] -> Some x
| x :: _ -> Some x
| [] -> None
(* Return the list of non-CDROM VDIs ie those which will be streamed-in *)
let non_cdrom_vdis (x: header) =
let all_vbds = List.filter (fun x -> x.cls = Datamodel._vbd) x.objects in
let all_vbds = List.map (fun x -> API.Legacy.From.vBD_t "" x.snapshot) all_vbds in
let all_disk_vbds = List.filter (fun x -> x.API.vBD_type <> `CD) all_vbds in
let all_disk_vdis = List.map (fun x -> Ref.string_of x.API.vBD_VDI) all_disk_vbds in
(* Remove all those whose SR has content-type = "iso" *)
let all_disk_vdis = List.filter (fun vdi ->
let vdir = API.Legacy.From.vDI_t "" (find_in_export vdi x.objects) in
let sr = API.Legacy.From.sR_t "" (find_in_export (Ref.string_of vdir.API.vDI_SR) x.objects) in
sr.API.sR_content_type <> "iso") all_disk_vdis in
let all_vdis = List.filter (fun x -> x.cls = Datamodel._vdi) x.objects in
List.filter (fun x -> false
|| (List.mem x.id all_disk_vdis)
|| (API.Legacy.From.vDI_t "" x.snapshot).API.vDI_type = `suspend) all_vdis
(* Check to see if another VM exists with the same MAC seed. *)
(* Check VM uuids don't already exist. Check that if a VDI exists then it is a CDROM. *)
let assert_can_restore_backup ~__context rpc session_id (x: header) =
let get_mac_seed vm =
if List.mem_assoc Xapi_globs.mac_seed vm.API.vM_other_config
then Some(List.assoc Xapi_globs.mac_seed vm.API.vM_other_config, vm)
else None in
let get_vm_uuid_of_snap s =
let snapshot_of = Ref.string_of s.API.vM_snapshot_of in
try
if Xstringext.String.startswith "Ref:" snapshot_of then
(* This should be a snapshot in the archive *)
let v = Listext.List.find (fun v -> v.cls = Datamodel._vm && v.id = snapshot_of) x.objects in
let v = API.Legacy.From.vM_t "" v.snapshot in
Some v.API.vM_uuid
else if Xstringext.String.startswith Ref.ref_prefix snapshot_of then
(* This should be a snapshot in a live system *)
if Db.is_valid_ref __context s.API.vM_snapshot_of then
Some (Db.VM.get_uuid ~__context ~self:s.API.vM_snapshot_of)
else
Some (List.assoc Db_names.uuid (Helpers.vm_string_to_assoc s.API.vM_snapshot_metadata))
else None
with _ -> None in
(* This function should be called when a VM/snapshot to import has the same
mac seed as an existing VM. They are considered compatible only in the
following cases:
- Both are VMs, and having the same uuid
- Both are snapshots, and the VMs they were derived from are the same one
- One is snapshot, one is VM, and the snapshot was derived from the VM
*)
let is_compatible v1 v2 =
match v1.API.vM_is_a_snapshot, v2.API.vM_is_a_snapshot with
| false, false ->
v1.API.vM_uuid = v2.API.vM_uuid
| true, true ->
let v1' = get_vm_uuid_of_snap v1 in
let v2' = get_vm_uuid_of_snap v2 in
v1' <> None && v2' <> None && v1' = v2'
| true, false ->
let v1' = get_vm_uuid_of_snap v1 in
v1' = Some v2.API.vM_uuid
| false, true ->
let v2' = get_vm_uuid_of_snap v2 in
v2' = Some v1.API.vM_uuid in
let import_vms =
Listext.List.filter_map
(fun x ->
if x.cls <> Datamodel._vm then None else
let x = API.Legacy.From.vM_t "" x.snapshot in
get_mac_seed x
) x.objects in
let existing_vms =
Listext.List.filter_map
(fun (_, v) -> get_mac_seed v)
(Client.VM.get_all_records rpc session_id) in
List.iter
(fun (mac, vm) ->
List.iter
(fun (mac', vm') ->
if mac = mac' && not (is_compatible vm vm') then
raise (Api_errors.Server_error(Api_errors.duplicate_vm, [ vm'.API.vM_uuid ])))
existing_vms)
import_vms
let assert_can_live_import __context rpc session_id state vm_record =
let pool = Helpers.get_pool ~__context in
let localhost = Helpers.get_localhost ~__context in
let assert_memory_available () =
let host_mem_available =
Memory_check.host_compute_free_memory_with_maximum_compression
~__context ~host:localhost None in
let main, shadow =
Memory_check.vm_compute_start_memory ~__context vm_record in
let mem_reqd_for_vm = Int64.add main shadow in
if host_mem_available < mem_reqd_for_vm then
raise (Api_errors.Server_error (
Api_errors.host_not_enough_free_memory,
[
Int64.to_string mem_reqd_for_vm;
Int64.to_string host_mem_available;
]))
in
let assert_ha_plan_exists () =
if Db.Pool.get_ha_enabled ~__context ~self:pool then begin
(* Determine which SRs and networks the VM will use. *)
(* These are required for the HA planner to determine the VM's agility. *)
let srs = vm_record.API.vM_VBDs
|> List.map (fun vbd -> find_in_export (Ref.string_of vbd) state.export)
|> List.map (API.Legacy.From.vBD_t "")
|> List.filter (fun vbd -> not vbd.API.vBD_empty)
|> List.map (fun vbd -> find_in_export (Ref.string_of vbd.API.vBD_VDI) state.export)
|> List.map (API.Legacy.From.vDI_t "")
|> List.filter (fun vdi -> List.mem_assoc Constants.storage_migrate_sr_map_key vdi.API.vDI_other_config)
|> List.map (fun vdi -> List.assoc Constants.storage_migrate_sr_map_key vdi.API.vDI_other_config)
|> List.map Ref.of_string
|> Listext.List.setify
in
let networks = vm_record.API.vM_VIFs
|> List.map (fun vif -> find_in_export (Ref.string_of vif) state.export)
|> List.map (API.Legacy.From.vIF_t "")
|> List.map
(fun vif ->
try
List.assoc Constants.storage_migrate_vif_map_key vif.API.vIF_other_config
|> Ref.of_string
with Not_found ->
failwith "no VIF mapped")
|> Listext.List.setify
in
let ha_vm = Agility.HA_VM.Not_in_db (localhost, srs, networks, vm_record) in
if vm_record.API.vM_ha_restart_priority = Constants.ha_restart
then Xapi_ha_vm_failover.assert_new_vm_preserves_ha_plan ~__context ha_vm
else
Xapi_ha_vm_failover.assert_vm_placement_preserves_ha_plan ~__context
~arriving:[localhost, ha_vm] ()
end
in
if vm_record.API.vM_power_state = `Running || vm_record.API.vM_power_state = `Paused
then assert_memory_available ();
assert_ha_plan_exists ()
(* The signature for a set of functions which we must provide to be able to import an object type. *)
module type HandlerTools = sig
(* A type which represents how we should deal with the import of an object. *)
type precheck_t
(* Compare the state of the database with the metadata to be imported. *)
(* Returns a result which signals what we should do to import the metadata. *)
val precheck: Context.t -> config -> (Rpc.call -> Rpc.response) -> API.ref_session -> state -> obj -> precheck_t
(* Handle the result of the precheck function, but don't create any database objects. *)
(* Add objects to the state table if necessary, to keep track of what would have been imported.*)
val handle_dry_run: Context.t -> config -> (Rpc.call -> Rpc.response) -> API.ref_session -> state -> obj -> precheck_t -> unit
(* Handle the result of the check function, creating database objects if necessary. *)
(* For certain combinations of result and object type, this can be aliased to handle_dry_run. *)
val handle: Context.t -> config -> (Rpc.call -> Rpc.response) -> API.ref_session -> state -> obj -> precheck_t -> unit
end
(* Make a handler for a set of handler functions. *)
module MakeHandler = functor (M: HandlerTools) -> struct
let handle __context config rpc session_id state obj =
let dry_run = match config.import_type with
| Metadata_import {dry_run = true; _} -> true
| _ -> false
in
let precheck_result = M.precheck __context config rpc session_id state obj in
if dry_run then
M.handle_dry_run __context config rpc session_id state obj precheck_result
else
M.handle __context config rpc session_id state obj precheck_result
end
module Host : HandlerTools = struct
type precheck_t =
| Found_host of API.ref_host
| Found_no_host
let precheck __context config rpc session_id state x =
let host_record = API.Legacy.From.host_t "" x.snapshot in
try Found_host (Db.Host.get_by_uuid __context host_record.API.host_uuid)
with _ -> Found_no_host
let handle_dry_run __context config rpc session_id state x precheck_result =
let host = match precheck_result with
| Found_host host' -> host'
| Found_no_host -> Ref.null
in
state.table <- (x.cls, x.id, Ref.string_of host) :: state.table
let handle = handle_dry_run
end
module VM : HandlerTools = struct
type precheck_t =
| Default_template of API.ref_VM
| Replace of API.ref_VM * API.vM_t
| Fail of exn
| Skip
| Clean_import of API.vM_t
let precheck __context config rpc session_id state x =
let vm_record = API.Legacy.From.vM_t "" x.snapshot in
if vm_record.API.vM_is_a_template
&& (List.mem_assoc Xapi_globs.default_template_key vm_record.API.vM_other_config)
&& ((List.assoc Xapi_globs.default_template_key vm_record.API.vM_other_config) = "true")
then begin
(* If the VM is a default template, then pick up the one with the same name. *)
let template =
try List.hd (Db.VM.get_by_name_label __context vm_record.API.vM_name_label)
with _ -> Ref.null
in
Default_template template
end else begin
let import_action =
(* Check for an existing VM with the same UUID - if one exists, what we do next *)
(* will depend on the state of the VM and whether the import is forced. *)
let get_vm_by_uuid () = Db.VM.get_by_uuid __context vm_record.API.vM_uuid in
let vm_uuid_exists () = try ignore (get_vm_by_uuid ()); true with _ -> false in
(* If full_restore is true then we want to keep the VM uuid - this may involve replacing an existing VM. *)
if config.full_restore && vm_uuid_exists () then begin
let vm = get_vm_by_uuid () in
(* The existing VM cannot be replaced if it is running. *)
(* If import is forced then skip the VM, else throw an error. *)
let power_state = Db.VM.get_power_state ~__context ~self:vm in
if power_state <> `Halted then begin
if config.force then
(debug "Forced import skipping VM %s as VM to replace was not halted." vm_record.API.vM_uuid; Skip)
else Fail (Api_errors.Server_error(Api_errors.vm_bad_power_state,
[
Ref.string_of vm;
Record_util.power_state_to_string `Halted;
Record_util.power_state_to_string power_state
]))
end else begin
(* The existing VM should not be replaced if the version to be imported is no newer, *)
(* unless the import is forced. *)
let existing_version = Db.VM.get_version ~__context ~self:vm in
let version_to_import = vm_record.API.vM_version in
if (existing_version >= version_to_import) && (config.force = false) then
Fail (Api_errors.Server_error(Api_errors.vm_to_import_is_not_newer_version,
[
Ref.string_of vm;
Int64.to_string existing_version;
Int64.to_string version_to_import;
]))
else
Replace (vm, vm_record)
end
end else
Clean_import vm_record
in
match import_action with
| Replace (_, vm_record) | Clean_import vm_record ->
if is_live config
then assert_can_live_import __context rpc session_id state vm_record;
import_action
| _ -> import_action
end
let handle_dry_run __context config rpc session_id state x precheck_result =
match precheck_result with
| Skip -> ()
| Fail e -> raise e
| Default_template template ->
state.table <- (x.cls, x.id, Ref.string_of template) :: state.table;
state.created_vms <- (x.cls, x.id, Ref.string_of template) :: state.created_vms
| Clean_import _ | Replace _ ->
let dummy_vm = Ref.make () in
state.table <- (x.cls, x.id, Ref.string_of dummy_vm) :: state.table
let handle __context config rpc session_id state x precheck_result =
(* This function assumes we've already checked for and dealt with any existing VM with the same UUID. *)
let do_import vm_record =
let task_id = Ref.string_of (Context.get_task_id __context) in
(* Remove the grant guest API access key unconditionally (it's only for our RHEL4 templates atm) *)
let other_config = List.filter
(fun (key, _) -> key <> Xapi_globs.grant_api_access) vm_record.API.vM_other_config in
(* If not performing a full restore then generate a fresh MAC seed *)
let other_config =
if config.full_restore
then other_config
else
(Xapi_globs.mac_seed, Uuid.string_of_uuid (Uuid.make_uuid ())) ::
(List.filter (fun (x, _) -> x <> Xapi_globs.mac_seed) other_config) in
let vm_record = { vm_record with API.vM_other_config = other_config } in
(* Preserve genid for cross-pool migrates, because to the guest the
* disk looks like it hasn't changed.
* Preserve genid for templates, since they're not going to be started.
* Generate a fresh genid for normal VM imports. *)
let vm_record =
if (is_live config) || vm_record.API.vM_is_a_template
then vm_record
else {
vm_record with API.vM_generation_id = Xapi_vm_helpers.fresh_genid
~current_genid:vm_record.API.vM_generation_id ()
}
in
let vm_record =
if vm_exported_pre_dmc x
then begin
let safe_constraints = Vm_memory_constraints.reset_to_safe_defaults
~constraints:(Vm_memory_constraints.extract ~vm_record) in
debug "VM %s was exported pre-DMC; dynamic_{min,max},target <- %Ld"
vm_record.API.vM_name_label safe_constraints.static_max;
{vm_record with API.
vM_memory_static_min = safe_constraints.static_min;
vM_memory_dynamic_min = safe_constraints.dynamic_min;
vM_memory_target = safe_constraints.target;
vM_memory_dynamic_max = safe_constraints.dynamic_max;
vM_memory_static_max = safe_constraints.static_max;
}
end else vm_record
in
let vm_record =
if vm_has_field ~x ~name:"has_vendor_device" then vm_record else (
{vm_record with API.vM_has_vendor_device = false;}
) in
let vm_record = {vm_record with API.
vM_memory_overhead = Memory_check.vm_compute_memory_overhead vm_record
} in
let vm_record = {vm_record with API.vM_protection_policy = Ref.null} in
(* Full restore preserves UUIDs, so if we are replacing an existing VM the version number should be incremented *)
(* to keep track of how many times this VM has been restored. If not a full restore, then we don't need to keep track. *)
let vm_record =
if config.full_restore then
{vm_record with API.vM_version = Int64.add vm_record.API.vM_version 1L}
else
{vm_record with API.vM_version = 0L}
in
(* Clear the appliance field - in the case of DR we will reconstruct the appliance separately. *)
let vm_record = {vm_record with API.vM_appliance = Ref.null} in
(* Correct ha-restart-priority for pre boston imports*)
let vm_record = match vm_record.API.vM_ha_restart_priority with
"0"|"1"|"2"|"3" as order -> { vm_record with API.vM_ha_restart_priority = "restart"; API.vM_order = Int64.of_string (order) }
| _ -> vm_record;
in
let vm = log_reraise
("failed to create VM with name-label " ^ vm_record.API.vM_name_label)
(fun value ->
let vm = Xapi_vm_helpers.create_from_record_without_checking_licence_feature_for_vendor_device
~__context rpc session_id value
in
if config.full_restore then Db.VM.set_uuid ~__context ~self:vm ~value:value.API.vM_uuid;
vm)
vm_record in
state.cleanup <- (fun __context rpc session_id ->
(* Need to get rid of the import task or we cannot destroy the VM *)
Helpers.log_exn_continue
(Printf.sprintf "Attempting to remove import from current_operations of VM: %s" (Ref.string_of vm))
(fun () -> Db.VM.remove_from_current_operations ~__context ~self:vm ~key:task_id) ();
Db.VM.set_power_state ~__context ~self:vm ~value:`Halted;
Client.VM.destroy rpc session_id vm) :: state.cleanup;
(* Restore the last_booted_record too (critical if suspended but might as well do it all the time) *)
Db.VM.set_last_booted_record ~__context ~self:vm ~value:(vm_record.API.vM_last_booted_record);
Db.VM.set_last_boot_CPU_flags ~__context ~self:vm ~value:(vm_record.API.vM_last_boot_CPU_flags);
TaskHelper.operate_on_db_task ~__context (fun t ->
(try Db.VM.remove_from_other_config ~__context ~self:vm ~key:Xapi_globs.import_task with _ -> ());
Db.VM.add_to_other_config ~__context ~self:vm ~key:Xapi_globs.import_task ~value:(Ref.string_of t));
(* Set the power_state and suspend_VDI if the VM is suspended.
* If anything goes wrong, still continue if forced. *)
if vm_record.API.vM_power_state = `Suspended then begin
try
let vdi = (lookup vm_record.API.vM_suspend_VDI) state.table in
Db.VM.set_power_state ~__context ~self:vm ~value:`Suspended;
Db.VM.set_suspend_VDI ~__context ~self:vm ~value:vdi
with e -> if not config.force then begin
Backtrace.is_important e;
let msg = "Failed to find VM's suspend_VDI: " ^ (Ref.string_of vm_record.API.vM_suspend_VDI) in
error "Import failed: %s" msg;
raise e
end
end else
Db.VM.set_power_state ~__context ~self:vm ~value:`Halted;
(* We might want to import a control domain *)
Db.VM.set_is_control_domain~__context ~self:vm ~value:vm_record.API.vM_is_control_domain;
Db.VM.set_resident_on ~__context ~self:vm ~value:(try lookup vm_record.API.vM_resident_on state.table with _ -> Ref.null);
Db.VM.set_affinity ~__context ~self:vm ~value:(try lookup vm_record.API.vM_affinity state.table with _ -> Ref.null);
(* Update the snapshot metadata. At this points, the snapshot_of field is not relevant as
it use the export ref. However, as the corresponding VM object may have not been created
yet, this fiels contains some useful information to update it later. *)
Db.VM.set_is_a_snapshot ~__context ~self:vm ~value:vm_record.API.vM_is_a_snapshot;
Db.VM.set_snapshot_info ~__context ~self:vm ~value:vm_record.API.vM_snapshot_info;
Db.VM.set_snapshot_of ~__context ~self:vm ~value:vm_record.API.vM_snapshot_of;
Db.VM.set_snapshot_time ~__context ~self:vm ~value:vm_record.API.vM_snapshot_time;
Db.VM.set_transportable_snapshot_id ~__context ~self:vm ~value:vm_record.API.vM_transportable_snapshot_id;
(* VM might have suspend_SR that does not exist on this pool *)
if None <> (Helpers.check_sr_exists ~__context
~self:vm_record.API.vM_suspend_SR)
then Db.VM.set_suspend_SR ~__context ~self:vm ~value:Ref.null ;
Db.VM.set_parent ~__context ~self:vm ~value:vm_record.API.vM_parent;
begin try
let gm = lookup vm_record.API.vM_guest_metrics state.table in
Db.VM.set_guest_metrics ~__context ~self:vm ~value:gm
with _ -> () end;
Db.VM.set_bios_strings ~__context ~self:vm ~value:vm_record.API.vM_bios_strings;
debug "Created VM: %s (was %s)" (Ref.string_of vm) x.id;
(* Although someone could sneak in here and attempt to power on the VM, it
doesn't really matter since no VBDs have been created yet.
We don't bother doing this if --force is set otherwise on error the VM
remains locked. *)
if not config.force then
Db.VM.add_to_current_operations ~__context ~self:vm ~key:task_id ~value:`import;
Xapi_vm_lifecycle.update_allowed_operations ~__context ~self:vm;
state.table <- (x.cls, x.id, Ref.string_of vm) :: state.table;
state.created_vms <- (x.cls, x.id, Ref.string_of vm) :: state.created_vms
in
match precheck_result with
| Skip | Fail _ | Default_template _ ->
handle_dry_run __context config rpc session_id state x precheck_result
| Clean_import (vm_record) -> do_import vm_record
| Replace (vm, vm_record) ->
(* Destroy the existing VM, along with its VIFs and VBDs. *)
debug "Replacing VM %s" vm_record.API.vM_uuid;
Helpers.call_api_functions ~__context
(fun rpc session_id ->
let vifs = Db.VM.get_VIFs ~__context ~self:vm in
List.iter (fun vif -> Client.VIF.destroy ~rpc ~session_id ~self:vif) vifs;
let vbds = Db.VM.get_VBDs ~__context ~self:vm in
List.iter (fun vbd -> Client.VBD.destroy ~rpc ~session_id ~self:vbd) vbds;
Client.VM.destroy ~rpc ~session_id ~self:vm);
do_import vm_record
end
(** Create the guest metrics *)
module GuestMetrics : HandlerTools = struct
type precheck_t = OK
let precheck __context config rpc session_id state x = OK
let handle_dry_run __context config rpc session_id state x precheck_result =
let dummy_gm = Ref.make () in
state.table <- (x.cls, x.id, Ref.string_of dummy_gm) :: state.table
let handle __context config rpc session_id state x precheck_result =
let gm_record = API.Legacy.From.vM_guest_metrics_t "" x.snapshot in
let gm = Ref.make () in
Db.VM_guest_metrics.create ~__context
~ref:gm
~uuid:(Uuid.to_string (Uuid.make_uuid ()))
~os_version:gm_record.API.vM_guest_metrics_os_version
~pV_drivers_version:gm_record.API.vM_guest_metrics_PV_drivers_version
~pV_drivers_up_to_date:gm_record.API.vM_guest_metrics_PV_drivers_up_to_date
~memory:gm_record.API.vM_guest_metrics_memory
~disks:gm_record.API.vM_guest_metrics_disks
~networks:gm_record.API.vM_guest_metrics_networks
~network_paths_optimized:gm_record.API.vM_guest_metrics_network_paths_optimized
~storage_paths_optimized:gm_record.API.vM_guest_metrics_storage_paths_optimized
~other:gm_record.API.vM_guest_metrics_other
~last_updated:gm_record.API.vM_guest_metrics_last_updated
~other_config:gm_record.API.vM_guest_metrics_other_config
~live:gm_record.API.vM_guest_metrics_live
~can_use_hotplug_vbd:gm_record.API.vM_guest_metrics_can_use_hotplug_vbd
~can_use_hotplug_vif:gm_record.API.vM_guest_metrics_can_use_hotplug_vif
;
state.table <- (x.cls, x.id, Ref.string_of gm) :: state.table
end
(** If we're restoring VM metadata only then lookup the SR by uuid. If we can't find
the SR then we will still try to match VDIs later (except CDROMs) *)
module SR : HandlerTools = struct
type precheck_t =
| Found_SR of API.ref_SR
| Found_no_SR
| Will_use_SR of API.ref_SR
| SR_not_needed
let precheck __context config rpc session_id state x =
let sr_record = API.Legacy.From.sR_t "" x.snapshot in
match config.import_type with
| Metadata_import _ -> begin
(* Look up the existing SR record *)
try
let sr = Client.SR.get_by_uuid rpc session_id sr_record.API.sR_uuid in
Found_SR sr
with e ->
let msg = match sr_record.API.sR_content_type with
| "iso" -> "- will eject disk" (* Will be handled specially in handle_vdi *)
| _ -> "- will still try to find individual VDIs"
in
warn "Failed to find SR with UUID: %s content-type: %s %s"
sr_record.API.sR_uuid sr_record.API.sR_content_type msg;
Found_no_SR
end
| Full_import sr -> begin
if sr_record.API.sR_content_type = "iso"
then SR_not_needed (* this one will be ejected *)
else Will_use_SR sr
end
let handle_dry_run __context config rpc session_id state x precheck_result =
match precheck_result with
| Found_SR sr | Will_use_SR sr ->
state.table <- (x.cls, x.id, Ref.string_of sr) :: state.table
| Found_no_SR | SR_not_needed -> ()
let handle = handle_dry_run
end
(** If we're restoring VM metadata only then lookup the VDI by uuid.
If restoring metadata only: lookup the VDI by location, falling back to content_id if available.
If importing everything: create a new VDI in the SR
On any error:
If the SR cannot be found then we skip this VDI.
If the SR can be found AND is an iso SR then we attempt to lookup the VDI by name_label
If the SR can be found AND is not an iso SR then we attempt to create the VDI in it *)
module VDI : HandlerTools = struct
type precheck_t =
| Found_iso of API.ref_VDI
| Found_no_iso
| Found_disk of API.ref_VDI
| Found_no_disk of exn
| Skip
| Create of API.vDI_t
let precheck __context config rpc session_id state x =
let vdi_record = API.Legacy.From.vDI_t "" x.snapshot in
let original_sr = API.Legacy.From.sR_t "" (find_in_export (Ref.string_of vdi_record.API.vDI_SR) state.export) in
if original_sr.API.sR_content_type = "iso" then begin
(* Best effort: locate a VDI in any shared ISO SR with a matching VDI.location *)
let iso_srs = List.filter (fun self -> Client.SR.get_content_type rpc session_id self = "iso"
&& Client.SR.get_type rpc session_id self <> "udev")
(Client.SR.get_all rpc session_id) in
match List.filter (fun (_, vdir) ->
vdir.API.vDI_location = vdi_record.API.vDI_location && (List.mem vdir.API.vDI_SR iso_srs))
(Client.VDI.get_all_records rpc session_id) |> choose_one with
| Some (vdi, _) ->
Found_iso vdi
| None ->
warn "Found no ISO VDI with location = %s; attempting to eject" vdi_record.API.vDI_location;
Found_no_iso
end else begin
match config.import_type with
| Metadata_import { vdi_map } -> begin
let mapto =
if List.mem_assoc Constants.storage_migrate_vdi_map_key vdi_record.API.vDI_other_config
then Some (Ref.of_string (List.assoc Constants.storage_migrate_vdi_map_key vdi_record.API.vDI_other_config))
else None in
let vdi_records = Client.VDI.get_all_records rpc session_id in
let find_by_sr_and_location sr location =
vdi_records
|> List.filter (fun (_, vdir) -> vdir.API.vDI_location = location && vdir.API.vDI_SR = sr)
|> choose_one
|> Opt.map fst in
let find_by_uuid uuid =
vdi_records
|> List.filter (fun (_, vdir) -> vdir.API.vDI_uuid = uuid)
|> choose_one
|> Opt.map fst in
let _scsiid = "SCSIid" in
let scsiid_of vdi_record =
if List.mem_assoc _scsiid vdi_record.API.vDI_sm_config
then Some (List.assoc _scsiid vdi_record.API.vDI_sm_config)
else None in
let find_by_scsiid x =
vdi_records
|> Listext.List.filter_map (fun (rf, vdir) ->
if scsiid_of vdir = Some x then Some (rf, vdir) else None)
|> choose_one in
let by_vdi_map =
(* Look up the mapping by both uuid and SCSIid *)
match (
if List.mem_assoc vdi_record.API.vDI_uuid vdi_map
then Some (List.assoc vdi_record.API.vDI_uuid vdi_map)
else match scsiid_of vdi_record with
| None -> None
| Some x ->
if List.mem_assoc x vdi_map
then Some (List.assoc x vdi_map)
else None
) with
| Some destination ->
begin match find_by_uuid destination with
| Some x -> Some x
| None ->
begin match find_by_scsiid destination with
| Some (rf, rc) ->
info "VDI %s (SCSIid %s) mapped to %s (SCSIid %s) by user" vdi_record.API.vDI_uuid (Opt.default "None" (scsiid_of vdi_record)) rc.API.vDI_uuid (Opt.default "None" (scsiid_of rc));
Some rf
| None -> None
end
end
| None ->
(match scsiid_of vdi_record with
| None -> None
| Some x ->
begin match find_by_scsiid x with
| Some (rf, rc) ->
info "VDI %s (SCSIid %s) mapped to %s (SCSIid %s) by user" vdi_record.API.vDI_uuid (Opt.default "None" (scsiid_of vdi_record)) rc.API.vDI_uuid (Opt.default "None" (scsiid_of rc));
Some rf
| None -> None
end
) in
match by_vdi_map with
| Some vdi ->
Found_disk vdi
| None ->
begin match (
if exists vdi_record.API.vDI_SR state.table then begin
let sr = lookup vdi_record.API.vDI_SR state.table in
match find_by_sr_and_location sr vdi_record.API.vDI_location with
| Some x -> Some x
| None -> mapto
end else mapto
) with
| Some vdi -> Found_disk vdi
| None -> begin
error "Found no VDI with location = %s: %s" vdi_record.API.vDI_location
(if config.force
then "ignoring error because '--force' is set"
else "treating as fatal and abandoning import");
if config.force then Skip
else begin
if exists vdi_record.API.vDI_SR state.table
then
let sr = lookup vdi_record.API.vDI_SR state.table in
Found_no_disk (Api_errors.Server_error(Api_errors.vdi_location_missing, [ Ref.string_of sr; vdi_record.API.vDI_location ]))
else Found_no_disk (Api_errors.Server_error(Api_errors.vdi_content_id_missing, [ ]))
end
end
end
end
| Full_import _ -> Create vdi_record
end
let handle_dry_run __context config rpc session_id state x precheck_result =
match precheck_result with
| Found_iso vdi | Found_disk vdi -> state.table <- (x.cls, x.id, Ref.string_of vdi) :: state.table
| Found_no_iso -> () (* VDI will be ejected. *)
| Found_no_disk e -> begin
match config.import_type with
| Metadata_import {live=true} ->
(* We expect the disk to be missing during a live migration dry run. *)
debug "Ignoring missing disk %s - this will be mirrored during a real live migration." x.id;
(* Create a dummy disk in the state table so the VBD import has a disk to look up. *)
let dummy_vdi = Ref.make () in
state.table <- (x.cls, x.id, Ref.string_of dummy_vdi) :: state.table
| _ -> raise e
end
| Skip -> ()
| Create _ ->
let dummy_vdi = Ref.make () in
state.table <- (x.cls, x.id, Ref.string_of dummy_vdi) :: state.table
let handle __context config rpc session_id state x precheck_result =
match precheck_result with
| Found_iso _ | Found_no_iso | Skip ->
handle_dry_run __context config rpc session_id state x precheck_result
| Found_disk vdi ->
handle_dry_run __context config rpc session_id state x precheck_result;
let other_config_record = (API.Legacy.From.vDI_t "" x.snapshot).API.vDI_other_config in
List.iter (fun key ->
Db.VDI.remove_from_other_config ~__context ~self:vdi ~key;
try Db.VDI.add_to_other_config ~__context ~self:vdi ~key ~value:(List.assoc key other_config_record) with Not_found -> ()
) Xapi_globs.vdi_other_config_sync_keys
| Found_no_disk e -> raise e
| Create vdi_record -> begin
(* Make a new VDI for streaming data into; adding task-id to sm-config on VDI.create so SM backend can see this is an import *)
let sr = lookup vdi_record.API.vDI_SR state.table in
let task_id = Ref.string_of (Context.get_task_id __context) in
let sm_config = List.filter (fun (k,_)->k<>Xapi_globs.import_task) vdi_record.API.vDI_sm_config in
let sm_config = (Xapi_globs.import_task, task_id)::sm_config in
let vdi = Client.VDI.create_from_record rpc session_id { vdi_record with API.vDI_SR = sr; API.vDI_sm_config = sm_config } in
state.cleanup <- (fun __context rpc session_id -> Client.VDI.destroy rpc session_id vdi) :: state.cleanup;
state.table <- (x.cls, x.id, Ref.string_of vdi) :: state.table
end
end
(** Lookup the network by name_label only. Previously we used UUID which worked if importing
to the same host that originated the export but would fail if the network UUID had changed
even if (from the user's PoV) the "backend network" had not. Since we don't model networks
it seems less confusing to match on names: whether networks are the same or different is then
under the control of the user. *)
module Net : HandlerTools = struct
type precheck_t =
| Found_net of API.ref_network
| Create of API.network_t
let precheck __context config rpc session_id state x =
let net_record = API.Legacy.From.network_t "" x.snapshot in
let possibilities = Client.Network.get_by_name_label rpc session_id net_record.API.network_name_label in
match possibilities with
| [] ->
begin
(* Lookup by bridge name as fallback *)
let expr = "field \"bridge\"=\"" ^ net_record.API.network_bridge ^ "\"" in
let nets = Client.Network.get_all_records_where rpc session_id expr in
match nets with
| [] -> Create net_record
| (net, _) :: _ -> Found_net net
end
| (n::ns) -> Found_net n
let handle_dry_run __context config rpc session_id state x precheck_result =
match precheck_result with
| Found_net net -> state.table <- (x.cls, x.id, Ref.string_of net) :: state.table
| Create _ ->
let dummy_net = Ref.make () in
state.table <- (x.cls, x.id, Ref.string_of dummy_net) :: state.table
let handle __context config rpc session_id state x precheck_result =
match precheck_result with
| Found_net _ ->
handle_dry_run __context config rpc session_id state x precheck_result
| Create net_record ->
let net =
log_reraise ("failed to create Network with name_label " ^ net_record.API.network_name_label)
(fun value -> Client.Network.create_from_record rpc session_id value) net_record
in
(* Only add task flag to networks which get created in this import *)
TaskHelper.operate_on_db_task ~__context
(fun t ->
(try Db.Network.remove_from_other_config ~__context ~self:net ~key:Xapi_globs.import_task
with _ -> ());
Db.Network.add_to_other_config ~__context ~self:net ~key:Xapi_globs.import_task
~value:(Ref.string_of t));
state.cleanup <- (fun __context rpc session_id ->
Client.Network.destroy rpc session_id net) :: state.cleanup;
state.table <- (x.cls, x.id, Ref.string_of net) :: state.table
end
(** Lookup the GPU group by GPU_types only. Currently, the GPU_types field contains the prototype
* of just a single pGPU. We would probably have to extend this function once we support GPU groups
* for multiple compatible GPU types. *)
module GPUGroup : HandlerTools = struct
type precheck_t =
| Found_GPU_group of API.ref_GPU_group
| Found_no_GPU_group of exn
| Create of API.gPU_group_t
let precheck __context config rpc session_id state x =
let gpu_group_record = API.Legacy.From.gPU_group_t "" x.snapshot in
let groups = Client.GPU_group.get_all_records rpc session_id in
try
let group, _ =
List.find (fun (_, groupr) ->
groupr.API.gPU_group_GPU_types = gpu_group_record.API.gPU_group_GPU_types) groups
in
Found_GPU_group group
with Not_found ->
match config.import_type with
| Metadata_import _ ->
(* In vm_metadata_only mode the GPU group must exist *)
let msg =
Printf.sprintf "Unable to find GPU group with matching GPU_types = '[%s]'"
(String.concat "," gpu_group_record.API.gPU_group_GPU_types)
in
error "%s" msg;
Found_no_GPU_group (Failure msg)
| Full_import _ ->
(* In normal mode we attempt to create any missing GPU groups *)
Create gpu_group_record
let handle_dry_run __context config rpc session_id state x precheck_result =
match precheck_result with
| Found_GPU_group group ->
state.table <- (x.cls, x.id, Ref.string_of group) :: state.table
| Found_no_GPU_group e -> raise e
| Create _ ->
let dummy_gpu_group = Ref.make () in
state.table <- (x.cls, x.id, Ref.string_of dummy_gpu_group) :: state.table
let handle __context config rpc session_id state x precheck_result =
match precheck_result with
| Found_GPU_group _ | Found_no_GPU_group _ ->
handle_dry_run __context config rpc session_id state x precheck_result
| Create gpu_group_record ->
let group = log_reraise ("Unable to create GPU group with GPU_types = '[%s]'" ^
(String.concat "," gpu_group_record.API.gPU_group_GPU_types)) (fun value ->
let group = Client.GPU_group.create ~rpc ~session_id
~name_label:value.API.gPU_group_name_label
~name_description:value.API.gPU_group_name_description
~other_config:value.API.gPU_group_other_config in
Db.GPU_group.set_GPU_types ~__context ~self:group ~value:value.API.gPU_group_GPU_types;
group
) gpu_group_record
in
(* Only add task flag to GPU groups which get created in this import *)
TaskHelper.operate_on_db_task ~__context (fun t ->
(try Db.GPU_group.remove_from_other_config ~__context ~self:group ~key:Xapi_globs.import_task
with _ -> ());
Db.GPU_group.add_to_other_config ~__context ~self:group ~key:Xapi_globs.import_task
~value:(Ref.string_of t));
state.cleanup <- (fun __context rpc session_id -> Client.GPU_group.destroy rpc session_id group) :: state.cleanup;
state.table <- (x.cls, x.id, Ref.string_of group) :: state.table
end
(** Create a new VBD record, add the reference to the table.
The VM and VDI must already have been handled first.
If the VDI doesn't exist and the VBD is a CDROM then eject it.
Note that any currently attached disk MUST be present, unless it's an HVM guest and a
CDROM in which case we eject it anyway.
*)
module VBD : HandlerTools = struct
type precheck_t =
| Found_VBD of API.ref_VBD
| Fail of exn
| Skip
| Create of API.vBD_t
let precheck __context config rpc session_id state x =
let vbd_record = API.Legacy.From.vBD_t "" x.snapshot in
let get_vbd () = Client.VBD.get_by_uuid rpc session_id vbd_record.API.vBD_uuid in
let vbd_exists () = try ignore (get_vbd ()); true with _ -> false in
if config.full_restore && vbd_exists () then begin
let vbd = get_vbd () in
Found_VBD vbd
end else begin
let vm = log_reraise
("Failed to find VBD's VM: " ^ (Ref.string_of vbd_record.API.vBD_VM))
(lookup vbd_record.API.vBD_VM) state.table in
(* If the VBD is supposed to be attached to a PV guest (which doesn't support
currently_attached empty drives) then throw a fatal error. *)
let original_vm = API.Legacy.From.vM_t "" (find_in_export (Ref.string_of vbd_record.API.vBD_VM) state.export) in
let has_booted_hvm =
let lbr = try Helpers.parse_boot_record original_vm.API.vM_last_booted_record with _ -> original_vm in
lbr.API.vM_HVM_boot_policy <> "" in
(* In the case of dry_run live migration, don't check for
missing disks as CDs will be ejected before the real migration. *)
let dry_run, live = match config.import_type with
| Metadata_import {dry_run = dry_run; live = live} -> dry_run, live
| _ -> false, false
in
if vbd_record.API.vBD_currently_attached && not(exists vbd_record.API.vBD_VDI state.table) then begin
(* It's only ok if it's a CDROM attached to an HVM guest, or it's part of SXM and we know the sender would eject it. *)
let will_eject = dry_run && live && original_vm.API.vM_power_state <> `Suspended in
if not (vbd_record.API.vBD_type = `CD && (has_booted_hvm || will_eject))
then raise (IFailure Attached_disks_not_found)
end;
let vbd_record = { vbd_record with API.vBD_VM = vm } in
match vbd_record.API.vBD_type, exists vbd_record.API.vBD_VDI state.table with
| `CD, false | `Floppy, false ->
if has_booted_hvm || original_vm.API.vM_power_state <> `Suspended then
Create { vbd_record with API.vBD_VDI = Ref.null; API.vBD_empty = true } (* eject *)
else
Create vbd_record
| `Disk, false -> begin
(* omit: cannot have empty disks *)
warn "Cannot import VM's disk: was it an .iso attached as a disk rather than CD?";
Skip
end
| _, true -> Create { vbd_record with API.vBD_VDI = lookup vbd_record.API.vBD_VDI state.table }
end
let handle_dry_run __context config rpc session_id state x precheck_result =
match precheck_result with
| Found_VBD vbd -> begin
state.table <- (x.cls, x.id, Ref.string_of vbd) :: state.table;