Skip to content

Commit ab5d30d

Browse files
committed
Implement VDI.get_nbd_info XenAPI call
Forward the call to master, because this call only does database accesses, but it does many of them, so it's more efficient to do this on the master. I have skipped empty IPs, because the IPs can be temporarily empty if they are configured via DHCP. And PIFs may not even have an IP, in which case it is the empty string again. In order to correctly format the IPv6 addresses in the output URIs and conform to the RFC3986 specification of URI syntax, which says that IPv6 addresses should be enclosed in square brackets, I had to construct the URIs by hand. I could not use the Uri library, because it would incorrectly escape the colon in the IPv6 addresses using a URL escape code. Signed-off-by: Gabor Igloi <[email protected]>
1 parent 9502abd commit ab5d30d

File tree

6 files changed

+153
-1
lines changed

6 files changed

+153
-1
lines changed

ocaml/idl/datamodel.ml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6617,6 +6617,17 @@ let vdi_export_changed_blocks = call
66176617
~allowed_roles:_R_VM_OP
66186618
()
66196619

6620+
let vdi_get_nbd_info = call
6621+
~name:"get_nbd_info"
6622+
~in_oss_since:None
6623+
~in_product_since:rel_inverness
6624+
~params:[Ref _vdi, "self", "The VDI to access via NBD."]
6625+
~errs: [Api_errors.vdi_incompatible_type]
6626+
~result:(Set String, "The list of URIs.")
6627+
~doc:"Get a list of URIs specifying how to access this VDI via the NBD server of XenServer. A URI will be returned for each PIF of each host that is connected to the VDI's SR. An empty list is returned in case no network has a PIF on a host with access to the relevant SR. To access the given VDI, any of the returned URIs can be passed to the NBD server running at the IP address and port specified by that URI as the export name."
6628+
~allowed_roles:_R_VM_ADMIN
6629+
()
6630+
66206631
(** A virtual disk *)
66216632
let vdi =
66226633
create_obj ~in_db:true ~in_product_since:rel_rio ~in_oss_since:oss_since_303 ~internal_deprecated_since:None ~persist:PersistEverything ~gen_constructor_destructor:true ~name:_vdi ~descr:"A virtual disk image"
@@ -6653,6 +6664,7 @@ let vdi =
66536664
vdi_disable_cbt;
66546665
vdi_data_destroy;
66556666
vdi_export_changed_blocks;
6667+
vdi_get_nbd_info;
66566668
]
66576669
~contents:
66586670
([ uid _vdi;

ocaml/xapi/message_forwarding.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3615,6 +3615,10 @@ module Forward = functor(Local: Custom_actions.CUSTOM_ACTIONS) -> struct
36153615
forward_vdi_op ~local_fn ~__context ~self:vdi_to
36163616
(fun session_id rpc -> Client.VDI.export_changed_blocks ~rpc ~session_id ~vdi_from ~vdi_to))
36173617

3618+
let get_nbd_info ~__context ~self =
3619+
info "VDI.get_nbd_info: vdi = '%s'" (vdi_uuid ~__context self);
3620+
Local.VDI.get_nbd_info ~__context ~self
3621+
36183622
end
36193623
module VBD = struct
36203624

ocaml/xapi/test_common.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,7 @@ let make_pool_update ~__context
378378
} in
379379
Xapi_pool_update.create_update_record ~__context ~update:ref ~update_info ~vdi;
380380
ref
381+
382+
let make_session ~__context ?(ref=Ref.make ()) ?(uuid=make_uuid ()) ?(this_host=Ref.null) ?(this_user=Ref.null) ?(last_active=API.Date.never) ?(pool=false) ?(other_config=[]) ?(is_local_superuser=false) ?(subject=Ref.null) ?(validation_time=API.Date.never) ?(auth_user_sid="") ?(auth_user_name="") ?(rbac_permissions=[]) ?(parent=Ref.null) ?(originator="test") () =
383+
Db.Session.create ~__context ~ref ~uuid ~this_host ~this_user ~last_active ~pool ~other_config ~is_local_superuser ~subject ~validation_time ~auth_user_sid ~auth_user_name ~rbac_permissions ~parent ~originator;
384+
ref

ocaml/xapi/test_vdi_cbt.ml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,108 @@ let test_clone_and_snapshot_correctly_sets_cbt_enabled_field () =
9494
let clone = Xapi_vdi.clone ~__context ~vdi ~driver_params:[] in
9595
assert_cbt_enabled_field_is ~vdi:clone ~value:false ~msg:"CBT should always be disabled on the VDI created by VDI.clone"
9696

97+
let test_get_nbd_info =
98+
let make_host __context sR ?(pbd_attached=true) pifs () =
99+
let host = Test_common.make_host ~__context () in
100+
let _: _ API.Ref.t = Test_common.make_pbd ~__context ~host ~sR ~currently_attached:pbd_attached () in
101+
List.iter (function (network, iP, iPv6, attached) ->
102+
let self = Test_common.make_pif ~__context ~network ~host ~iP ~iPv6 () in
103+
Db.PIF.set_currently_attached ~__context ~self ~value:attached
104+
) pifs;
105+
host
106+
in
107+
108+
let setup_test () =
109+
let __context = Mock.make_context_with_new_db "Mock context" in
110+
let session_id = Test_common.make_session ~__context () in
111+
let __context = Context.make ~__context ~session_id "test" in
112+
let make_host = make_host __context in
113+
114+
let sr_of_vdi = Test_common.make_sr ~__context () in
115+
let network = Test_common.make_network ~__context () in
116+
let vdi = Test_common.make_vdi ~__context ~sR:sr_of_vdi () in
117+
118+
(__context, make_host, sr_of_vdi, network, vdi)
119+
in
120+
121+
let test_returns_correct_uris () =
122+
let (__context, make_host, sr_of_vdi, network_1, self) = setup_test () in
123+
let uuid = Db.VDI.get_uuid ~__context ~self in
124+
let other_sr = Test_common.make_sr ~__context () in
125+
let network_2 = Test_common.make_network ~__context () in
126+
127+
(* Hosts connected to both the VDI's SR and a network *)
128+
let _: _ API.Ref.t = make_host
129+
sr_of_vdi
130+
[(network_1, "92.40.98.91", [], true); (network_1, "92.40.98.92", [], true);
131+
(* this PIF is not currently attached: *)
132+
(network_2, "92.40.98.93", ["10e1:bdb8:05a3:0002:03ae:8a24:0371:0001"], false)] () in
133+
let _: _ API.Ref.t = make_host
134+
sr_of_vdi
135+
[(network_2, "92.40.98.94", ["10e1:bdb8:05a3:0002:03ae:8a24:0371:0002";"10e1:bdb8:05a3:0002:03ae:8a24:0371:0003"], true)] () in
136+
(* Hosts not connected to a network or without an IP address *)
137+
let _: _ API.Ref.t = make_host sr_of_vdi [(network_1, "92.40.98.95", ["10e1:bdb8:05a3:0002:03ae:8a24:0371:0004"], false)] () in
138+
let _: _ API.Ref.t = make_host sr_of_vdi [(network_1, "", [], true)] () in
139+
(* Hosts not connected to the VDI's SR *)
140+
let _: _ API.Ref.t = make_host sr_of_vdi ~pbd_attached:false [(network_1, "92.40.98.96", [], true)] () in
141+
let _: _ API.Ref.t = make_host other_sr [(network_1, "92.40.98.97", ["10e1:bdb8:05a3:0002:03ae:8a24:0371:0005"], true)] () in
142+
(* Hosts not connected to either *)
143+
let _: _ API.Ref.t = make_host sr_of_vdi [] () in
144+
145+
let nbd_info = Xapi_vdi.get_nbd_info ~__context ~self in
146+
147+
let session_id = Context.get_session_id __context |> Ref.string_of in
148+
let expected =
149+
[ "nbd://92.40.98.91:10809/" ^ uuid ^ "?session_id=" ^ session_id
150+
; "nbd://92.40.98.92:10809/" ^ uuid ^ "?session_id=" ^ session_id
151+
; "nbd://92.40.98.94:10809/" ^ uuid ^ "?session_id=" ^ session_id
152+
; "nbd://[10e1:bdb8:05a3:0002:03ae:8a24:0371:0002]:10809/" ^ uuid ^ "?session_id=" ^ session_id
153+
; "nbd://[10e1:bdb8:05a3:0002:03ae:8a24:0371:0003]:10809/" ^ uuid ^ "?session_id=" ^ session_id
154+
]
155+
in
156+
let sort = List.sort String.compare in
157+
OUnit.assert_equal ~printer:(String.concat ", ") (sort expected) (sort nbd_info)
158+
in
159+
160+
let test_returns_empty_list_when_no_host_is_connected_to_sr () =
161+
let (__context, make_host, sr_of_vdi, network, self) = setup_test () in
162+
let _: _ API.Ref.t = make_host sr_of_vdi ~pbd_attached:false [(network, "92.40.98.93", [], true)] () in
163+
OUnit.assert_equal [] (Xapi_vdi.get_nbd_info ~__context ~self);
164+
Db.PBD.get_all ~__context |> List.iter (fun self -> Db.PBD.destroy ~__context ~self);
165+
OUnit.assert_equal [] (Xapi_vdi.get_nbd_info ~__context ~self)
166+
in
167+
168+
let test_returns_empty_list_when_no_host_is_connected_to_network () =
169+
let (__context, make_host, sr_of_vdi, network, self) = setup_test () in
170+
let _: _ API.Ref.t = make_host sr_of_vdi [(network, "92.40.98.93", [], false)] () in
171+
OUnit.assert_equal [] (Xapi_vdi.get_nbd_info ~__context ~self);
172+
Db.PIF.get_all ~__context |> List.iter (fun self -> Db.PIF.destroy ~__context ~self);
173+
OUnit.assert_equal [] (Xapi_vdi.get_nbd_info ~__context ~self)
174+
in
175+
176+
let test_disallowed_for_cbt_metadata_vdi () =
177+
let (__context, make_host, sr_of_vdi, network, self) = setup_test () in
178+
let _: _ API.Ref.t = make_host sr_of_vdi [(network, "92.40.98.93", [], true)] () in
179+
Db.VDI.set_type ~__context ~self ~value:`cbt_metadata;
180+
let _type = Record_util.vdi_type_to_string `cbt_metadata in
181+
OUnit.assert_raises
182+
Api_errors.(Server_error (vdi_incompatible_type, [Ref.string_of self; _type]))
183+
(fun () -> Xapi_vdi.get_nbd_info ~__context ~self)
184+
in
185+
186+
let open OUnit in
187+
"test_get_nbd_info" >:::
188+
[ "test_returns_correct_uris" >:: test_returns_correct_uris
189+
; "test_returns_empty_list_when_no_host_is_connected_to_sr" >:: test_returns_empty_list_when_no_host_is_connected_to_sr
190+
; "test_returns_empty_list_when_no_host_is_connected_to_network" >:: test_returns_empty_list_when_no_host_is_connected_to_network
191+
; "test_disallowed_for_cbt_metadata_vdi" >:: test_disallowed_for_cbt_metadata_vdi
192+
]
193+
97194
let test =
98195
let open OUnit in
99196
"test_vdi_cbt" >:::
100197
[ "test_cbt_enable_disable" >:: test_cbt_enable_disable
101198
; "test_set_metadata_of_pool_doesnt_allow_cbt_metadata_vdi" >:: test_set_metadata_of_pool_doesnt_allow_cbt_metadata_vdi
102199
; "test_clone_and_snapshot_correctly_sets_cbt_enabled_field" >:: test_clone_and_snapshot_correctly_sets_cbt_enabled_field
200+
; test_get_nbd_info
103201
]

ocaml/xapi/xapi_vdi.ml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,38 @@ let export_changed_blocks ~__context ~vdi_from ~vdi_to =
870870
Storage_access.transform_storage_exn
871871
(fun () ->
872872
C.VDI.export_changed_blocks ~dbg:(Ref.string_of task) ~sr ~vdi_from ~vdi_to
873-
);
873+
)
874+
875+
let get_nbd_info ~__context ~self =
876+
if (Db.VDI.get_type ~__context ~self) = `cbt_metadata then begin
877+
error "VDI.get_nbd_info: called with a VDI of type cbt_metadata (at %s)" __LOC__;
878+
raise (Api_errors.Server_error (Api_errors.vdi_incompatible_type, [ Ref.string_of self; Record_util.vdi_type_to_string `cbt_metadata ]))
879+
end;
880+
881+
let sr = Db.VDI.get_SR ~__context ~self in
882+
let hosts_with_attached_pbds =
883+
Db.SR.get_PBDs ~__context ~self:sr
884+
|> List.filter (fun pbd -> Db.PBD.get_currently_attached ~__context ~self:pbd)
885+
|> List.map (fun pbd -> Db.PBD.get_host ~__context ~self:pbd)
886+
in
887+
let get_ips host =
888+
let get_ips pif =
889+
let not_empty = (<>) "" in
890+
let v6_ips = Db.PIF.get_IPv6 ~__context ~self:pif |> List.filter not_empty |> List.map (fun s -> "[" ^ s ^ "]") in
891+
let v4_ip = Db.PIF.get_IP ~__context ~self:pif in
892+
if not_empty v4_ip then v4_ip :: v6_ips else v6_ips
893+
in
894+
let attached_pifs =
895+
Db.Host.get_PIFs ~__context ~self:host
896+
|> List.filter (fun pif -> Db.PIF.get_currently_attached ~__context ~self:pif)
897+
in
898+
attached_pifs |> List.map get_ips |> List.flatten
899+
in
900+
let ips = hosts_with_attached_pbds |> List.map get_ips |> List.flatten in
901+
902+
let vdi_uuid = Db.VDI.get_uuid ~__context ~self in
903+
let session_id = Context.get_session_id __context |> Ref.string_of in
904+
ips
905+
|> List.map (fun ip -> Printf.sprintf "nbd://%s:10809/%s?session_id=%s" ip vdi_uuid session_id)
874906

875907
(* let pool_migrate = "See Xapi_vm_migrate.vdi_pool_migrate!" *)

ocaml/xapi/xapi_vdi.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,5 @@ val disable_cbt :
212212
__context:Context.t -> self:API.ref_VDI -> unit
213213
val export_changed_blocks :
214214
__context:Context.t -> vdi_from:API.ref_VDI -> vdi_to:API.ref_VDI -> string
215+
val get_nbd_info :
216+
__context:Context.t -> self:API.ref_VDI -> string list

0 commit comments

Comments
 (0)