/
import.ml
2393 lines (2293 loc) · 89.7 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
module Listext = Xapi_stdext_std.Listext
module Xstringext = Xapi_stdext_std.Xstringext
module Unixext = Xapi_stdext_unix.Unixext
open Http
open Importexport
open Xapi_stdext_unix.Unixext
open Xapi_stdext_pervasives.Pervasiveext
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 | _ -> 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}
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_common._vbd) x.objects
in
let all_vbds = List.map (fun x -> API.vBD_t_of_rpc 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.vDI_t_of_rpc (find_in_export vdi x.objects) in
let sr =
API.sR_t_of_rpc
(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_common._vdi) x.objects
in
List.filter
(fun x ->
false
|| List.mem x.id all_disk_vdis
|| (API.vDI_t_of_rpc x.snapshot).API.vDI_type = `suspend
)
all_vdis
let get_vm_record snapshot =
let vm_record = API.vM_t_of_rpc snapshot in
(* Ensure that the domain_type is set correctly *)
if vm_record.API.vM_domain_type = `unspecified then
{
vm_record with
API.vM_domain_type=
Xapi_vm_helpers.derive_domain_type
~hVM_boot_policy:vm_record.API.vM_HVM_boot_policy
}
else
vm_record
(* 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 =
List.find
(fun v -> v.cls = Datamodel_common._vm && v.id = snapshot_of)
x.objects
in
let v = get_vm_record 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 =
List.filter_map
(fun x ->
if x.cls <> Datamodel_common._vm then
None
else
let x = get_vm_record x.snapshot in
get_mac_seed x
)
x.objects
in
let existing_vms =
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 vm_record =
let assert_memory_available () =
let host = Helpers.get_localhost ~__context in
let host_mem_available =
Memory_check.host_compute_free_memory_with_maximum_compression ~__context
~host 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
if
vm_record.API.vM_power_state = `Running
|| vm_record.API.vM_power_state = `Paused
then
assert_memory_available ()
(* Assert that the local host, which is the host we are live-migrating the VM to,
* has free capacity on a PGPU from the given VGPU's GPU group. *)
let assert_can_live_import_vgpu ~__context vgpu_record =
let host = Helpers.get_localhost ~__context in
let local_pgpus =
Db.PGPU.get_refs_where ~__context
~expr:
Db_filter_types.(
And
( Eq
( Field "GPU_group"
, Literal (Ref.string_of vgpu_record.API.vGPU_GPU_group)
)
, Eq (Field "host", Literal (Ref.string_of host))
)
)
in
let capacity_exists =
List.exists
(fun pgpu ->
try
Xapi_pgpu_helpers.assert_capacity_exists_for_VGPU_type ~__context
~self:pgpu ~vgpu_type:vgpu_record.API.vGPU_type ;
true
with _ -> false
)
local_pgpus
in
if not capacity_exists then
raise
Api_errors.(
Server_error
( vm_requires_gpu
, [
Ref.string_of vgpu_record.API.vGPU_VM
; Ref.string_of vgpu_record.API.vGPU_GPU_group
]
)
)
(* 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.host_t_of_rpc 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 = get_vm_record x.snapshot in
let is_default_template =
vm_record.API.vM_is_default_template
|| 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"
in
if is_default_template then
(* 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
else
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
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
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
]
)
)
else
(* 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)
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 vm_record ;
import_action
| _ ->
import_action
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 (
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
}
) 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
(* Initialize platform["device-model"] if it is not set *)
let vm_record =
{
vm_record with
API.vM_platform=
Xapi_vm_helpers.ensure_device_model_profile_present ~__context
~domain_type:vm_record.API.vM_domain_type
~is_a_template:vm_record.API.vM_is_a_template
vm_record.API.vM_platform
}
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 (
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 ;
let vm_metrics = Db.VM.get_metrics ~__context ~self:vm in
Db.VM_metrics.set_current_domain_type ~__context ~self:vm_metrics
~value:vm_record.API.vM_domain_type
with e ->
if not config.force then (
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
)
) 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 ;
( try
let gm = lookup vm_record.API.vM_guest_metrics state.table in
Db.VM.set_guest_metrics ~__context ~self:vm ~value:gm
with _ -> ()
) ;
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.vM_guest_metrics_t_of_rpc 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
~pV_drivers_detected:gm_record.API.vM_guest_metrics_PV_drivers_detected
~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.sR_t_of_rpc x.snapshot in
match config.import_type with
| Metadata_import _ -> (
try
(* Look up the existing SR record *)
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
)
| Full_import sr ->
if sr_record.API.sR_content_type = "iso" then
SR_not_needed (* this one will be ejected *)
else
Will_use_SR sr
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.vDI_t_of_rpc x.snapshot in
let original_sr =
API.sR_t_of_rpc
(find_in_export (Ref.string_of vdi_record.API.vDI_SR) state.export)
in
if original_sr.API.sR_content_type = "iso" then (
(* 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
) else
match config.import_type with
| Metadata_import {vdi_map} -> (
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
|> Option.map fst
in
let find_by_uuid uuid =
vdi_records
|> List.filter (fun (_, vdir) -> vdir.API.vDI_uuid = uuid)
|> choose_one
|> Option.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
|> 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 -> (
match find_by_uuid destination with
| Some x ->
Some x
| None -> (
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
(Option.value ~default:"None" (scsiid_of vdi_record))
rc.API.vDI_uuid
(Option.value ~default:"None" (scsiid_of rc)) ;
Some rf
| None ->
None
)
)
| None -> (
match scsiid_of vdi_record with
| None ->
None
| Some x -> (
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
(Option.value ~default:"None" (scsiid_of vdi_record))
rc.API.vDI_uuid
(Option.value ~default:"None" (scsiid_of rc)) ;
Some rf
| None ->
None
)
)
in
match by_vdi_map with
| Some vdi ->
Found_disk vdi
| None -> (
match
if exists vdi_record.API.vDI_SR state.table then
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
else
mapto
with
| Some vdi ->
Found_disk vdi
| None ->