/
xapi_vbd.ml
237 lines (201 loc) · 10.6 KB
/
xapi_vbd.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
(*
* 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.
*)
(** Module that defines API functions for VBD objects
* @group XenAPI functions
*)
open Stringext
open Xapi_vbd_helpers
open Vbdops
open Threadext
open D
let assert_operation_valid ~__context ~self ~(op:API.vbd_operations) =
assert_operation_valid ~__context ~self ~op
let update_allowed_operations ~__context ~self : unit =
update_allowed_operations ~__context ~self
let assert_attachable ~__context ~self : unit =
assert_attachable ~__context ~self
let set_mode ~__context ~self ~value =
let vm = Db.VBD.get_VM ~__context ~self in
let power_state = Db.VM.get_power_state ~__context ~self:vm in
if power_state <> `Halted
then raise (Api_errors.Server_error(Api_errors.vm_bad_power_state, [Ref.string_of vm; Record_util.power_to_string `Halted; Record_util.power_to_string power_state]));
Db.VBD.set_mode ~__context ~self ~value
let plug ~__context ~self =
let vm = Db.VBD.get_VM ~__context ~self in
let domid = Int64.to_int (Db.VM.get_domid ~__context ~self:vm) in
let force_loopback_vbd = Helpers.force_loopback_vbd ~__context in
let hvm = Helpers.has_booted_hvm ~__context ~self:vm in
if System_domains.storage_driver_domain_of_vbd ~__context ~vbd:self = vm && not force_loopback_vbd then begin
debug "VBD.plug of loopback VBD '%s'" (Ref.string_of self);
Storage_access.attach_and_activate ~__context ~vbd:self ~domid ~hvm
(fun attach_info ->
let params = attach_info.Storage_interface.params in
let prefix = "/dev/" in
let prefix_len = String.length prefix in
let path = String.sub params prefix_len (String.length params - prefix_len) in
Db.VBD.set_device ~__context ~self ~value:path;
Db.VBD.set_currently_attached ~__context ~self ~value:true;
)
end
else begin
(* CA-83260: prevent HVM guests having readonly disk VBDs *)
let dev_type = Db.VBD.get_type ~__context ~self in
let mode = Db.VBD.get_mode ~__context ~self in
if hvm && dev_type <> `CD && mode = `RO then
raise (Api_errors.Server_error(Api_errors.disk_vbd_must_be_readwrite_for_hvm, [ Ref.string_of self ]));
Xapi_xenops.vbd_plug ~__context ~self
end
let unplug ~__context ~self =
let vm = Db.VBD.get_VM ~__context ~self in
let force_loopback_vbd = Helpers.force_loopback_vbd ~__context in
if System_domains.storage_driver_domain_of_vbd ~__context ~vbd:self = vm && not force_loopback_vbd then begin
debug "VBD.unplug of loopback VBD '%s'" (Ref.string_of self);
let domid = Int64.to_int (Db.VM.get_domid ~__context ~self:vm) in
Storage_access.deactivate_and_detach ~__context ~vbd:self ~domid;
Db.VBD.set_currently_attached ~__context ~self ~value:false
end
else Xapi_xenops.vbd_unplug ~__context ~self false
let unplug_force ~__context ~self =
let vm = Db.VBD.get_VM ~__context ~self in
let force_loopback_vbd = Helpers.force_loopback_vbd ~__context in
if System_domains.storage_driver_domain_of_vbd ~__context ~vbd:self = vm && not force_loopback_vbd
then unplug ~__context ~self
else Xapi_xenops.vbd_unplug ~__context ~self true
let unplug_force_no_safety_check ~__context ~self =
let vm = Db.VBD.get_VM ~__context ~self in
let force_loopback_vbd = Helpers.force_loopback_vbd ~__context in
if System_domains.storage_driver_domain_of_vbd ~__context ~vbd:self = vm && not force_loopback_vbd
then unplug ~__context ~self
else Xapi_xenops.vbd_unplug ~__context ~self true
(** Hold this mutex while resolving the 'autodetect' device names to prevent two concurrent
VBD.creates racing with each other and choosing the same device. For simplicity keep this
as a global lock rather than a per-VM one. Rely on the fact that the message forwarding layer
always runs this code on the master. *)
let autodetect_mutex = Mutex.create ()
(** VBD.create doesn't require any interaction with xen *)
let create ~__context ~vM ~vDI ~userdevice ~bootable ~mode ~_type ~unpluggable ~empty
~other_config ~qos_algorithm_type ~qos_algorithm_params =
if not empty then begin
let vdi_type = Db.VDI.get_type ~__context ~self:vDI in
if not(List.mem vdi_type [ `system; `user; `ephemeral; `suspend; `crashdump; `metadata])
then raise (Api_errors.Server_error(Api_errors.vdi_incompatible_type, [ Ref.string_of vDI; Record_util.vdi_type_to_string vdi_type ]))
end;
(* All "CD" VBDs must be readonly *)
if _type = `CD && mode <> `RO
then raise (Api_errors.Server_error(Api_errors.vbd_cds_must_be_readonly, []));
(* Only "CD" VBDs may be empty *)
if _type <> `CD && empty
then raise (Api_errors.Server_error(Api_errors.vbd_not_removable_media, [ "in constructor" ]));
(* Prevent VBDs being created which are of type "CD" which are
not either .iso files or CD block devices *)
if _type = `CD && not(empty)
then Xapi_vdi_helpers.assert_vdi_is_valid_iso ~__context ~vdi:vDI;
(* Prevent RW VBDs being created pointing to RO VDIs *)
if mode = `RW && Db.VDI.get_read_only ~__context ~self:vDI
then raise (Api_errors.Server_error(Api_errors.vdi_readonly, [ Ref.string_of vDI ]));
(* CA-75697: Disallow VBD.create on a VM that's in the middle of a migration *)
debug "Checking whether there's a migrate in progress...";
let vm_current_ops = Listext.List.setify (List.map snd (Db.VM.get_current_operations ~__context ~self:vM)) in
let migrate_ops = [ `migrate_send; `pool_migrate ] in
let migrate_ops_in_progress = List.filter (fun op -> List.mem op vm_current_ops) migrate_ops in
match migrate_ops_in_progress with
| op::_ ->
raise
(Api_errors.Server_error(Api_errors.other_operation_in_progress, [
"VM"; Ref.string_of vM; Record_util.vm_operation_to_string op ]));
| _ ->
Mutex.execute autodetect_mutex
(fun () ->
let possibilities = Xapi_vm_helpers.allowed_VBD_devices ~__context ~vm:vM in
if not (valid_device userdevice) || (userdevice = "autodetect" && possibilities = []) then
raise (Api_errors.Server_error (Api_errors.invalid_device,[userdevice]));
(* Resolve the "autodetect" into a fixed device name now *)
let userdevice = if userdevice = "autodetect"
then string_of_int (Device_number.to_disk_number (List.hd possibilities)) (* already checked for [] above *)
else userdevice in
let uuid = Uuid.insecure () in
let ref = Ref.insecure () in
debug "VBD.create (device = %s; uuid = %s; ref = %s)"
userdevice (Uuid.string_of_uuid uuid) (Ref.string_of ref);
(* Check that the device is definitely unique. If the requested device is numerical
(eg 1) then we 'expand' it into other possible names (eg 'hdb' 'xvdb') to detect
all possible clashes. *)
let userdevices = Xapi_vm_helpers.possible_VBD_devices_of_string userdevice in
let existing_devices = Xapi_vm_helpers.all_used_VBD_devices ~__context ~self:vM in
if Listext.List.intersect userdevices existing_devices <> []
then raise (Api_errors.Server_error (Api_errors.device_already_exists, [userdevice]));
(* Make people aware that non-shared disks make VMs not agile *)
if not empty then assert_doesnt_make_vm_non_agile ~__context ~vm:vM ~vdi:vDI;
let metrics = Ref.insecure () and metrics_uuid = Uuid.to_string (Uuid.insecure ()) in
Db.VBD_metrics.create ~__context ~ref:metrics ~uuid:metrics_uuid
~io_read_kbs:0. ~io_write_kbs:0. ~last_updated:(Date.of_float 0.)
~other_config:[];
Db.VBD.create ~__context ~ref ~uuid:(Uuid.to_string uuid)
~current_operations:[] ~allowed_operations:[] ~storage_lock:false
~vM ~vDI ~userdevice ~device:"" ~bootable ~mode ~_type ~unpluggable ~empty ~reserved:false
~qos_algorithm_type ~qos_algorithm_params ~qos_supported_algorithms:[]
~currently_attached:false
~status_code:Int64.zero ~status_detail:""
~runtime_properties:[] ~other_config
~metrics;
update_allowed_operations ~__context ~self:ref;
ref
)
let destroy ~__context ~self = destroy ~__context ~self
(** Throws VBD_NOT_REMOVABLE_ERROR if the VBD doesn't represent removable
media. Currently this just means "CD" but might change in future? *)
let assert_removable ~__context ~vbd =
if not(Helpers.is_removable ~__context ~vbd)
then raise (Api_errors.Server_error(Api_errors.vbd_not_removable_media, [ Ref.string_of vbd ]))
(** Throws VBD_NOT_EMPTY if the VBD already has a VDI *)
let assert_empty ~__context ~vbd =
if not(Db.VBD.get_empty ~__context ~self:vbd)
then raise (Api_errors.Server_error(Api_errors.vbd_not_empty, [ Ref.string_of vbd ]))
(** Throws VBD_IS_EMPTY if the VBD has no VDI *)
let assert_not_empty ~__context ~vbd =
if Db.VBD.get_empty ~__context ~self:vbd
then raise (Api_errors.Server_error(Api_errors.vbd_is_empty, [ Ref.string_of vbd ]))
(** Throws BAD_POWER_STATE if the VM is suspended *)
let assert_not_suspended ~__context ~vm =
if (Db.VM.get_power_state ~__context ~self:vm)=`Suspended then
let expected = String.concat ", " (List.map Record_util.power_to_string [`Halted; `Running]) in
let error_params = [Ref.string_of vm; expected; Record_util.power_to_string `Suspended] in
raise (Api_errors.Server_error(Api_errors.vm_bad_power_state, error_params))
let assert_ok_to_insert ~__context ~vbd ~vdi =
let vm = Db.VBD.get_VM ~__context ~self:vbd in
assert_not_suspended ~__context ~vm;
assert_removable ~__context ~vbd;
assert_empty ~__context ~vbd;
Xapi_vdi_helpers.assert_vdi_is_valid_iso ~__context ~vdi;
Xapi_vdi_helpers.assert_managed ~__context ~vdi;
assert_doesnt_make_vm_non_agile ~__context ~vm ~vdi
let insert ~__context ~vbd ~vdi =
assert_ok_to_insert ~__context ~vbd ~vdi;
Xapi_xenops.vbd_insert ~__context ~self:vbd ~vdi
let assert_ok_to_eject ~__context ~vbd =
let vm = Db.VBD.get_VM ~__context ~self:vbd in
assert_removable ~__context ~vbd;
assert_not_empty ~__context ~vbd;
assert_not_suspended ~__context ~vm
let eject ~__context ~vbd =
assert_ok_to_eject ~__context ~vbd;
Xapi_xenops.vbd_eject ~__context ~self:vbd
open Threadext
open Pervasiveext
open Fun
let pause ~__context ~self =
let vdi = Db.VBD.get_VDI ~__context ~self in
let sr = Db.VDI.get_SR ~__context ~self:vdi |> Ref.string_of in
raise (Api_errors.Server_error(Api_errors.sr_operation_not_supported, [ sr ]))
let unpause = pause