Skip to content

Commit d5a8900

Browse files
committed
CA-171948: Reinstate non-idempotency, with a switch
* Xapi code must assume idempotency of 'add_to_<map>' calls * Remote DB calls will _always_ be idempotent * DB calls on the master are controlled by the switch, defaulting to the original behaviour (non idempotent) * API calls follow the master DB behaviour Signed-off-by: Jon Ludlam <[email protected]>
1 parent 5f754cb commit d5a8900

File tree

9 files changed

+65
-6
lines changed

9 files changed

+65
-6
lines changed

ocaml/database/db_cache_impl.ml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,24 @@ let process_structured_field_locked t (key,value) tblname fld objref proc_fn_sel
254254
let newval = match proc_fn_selector with
255255
| AddSet -> add_to_set key existing_str
256256
| RemoveSet -> remove_from_set key existing_str
257-
| AddMap ->
257+
| AddMap | AddMapLegacy ->
258258
begin
259259
try
260-
add_to_map false key value existing_str
260+
(* We use the idempotent map add if we're using the non-legacy
261+
process function, or if the global field 'idempotent_map' has
262+
been set. By default, the Db calls on the master use the
263+
legacy functions, but those on the slave use the new one.
264+
This means xapi code should always assume idempotent_map is
265+
true *)
266+
let idempotent =
267+
(proc_fn_selector = AddMap) || !Db_globs.idempotent_map
268+
in
269+
add_to_map idempotent key value existing_str
261270
with Duplicate ->
262271
error "Duplicate key in set or map: table %s; field %s; ref %s; key %s" tblname fld objref key;
263272
raise (Duplicate_key (tblname,fld,objref,key));
264273
end
274+
265275
| RemoveMap -> remove_from_map key existing_str in
266276
write_field_locked t tblname objref fld newval
267277
with Not_found ->

ocaml/database/db_cache_types.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,4 +500,5 @@ type structured_op_t =
500500
| RemoveSet
501501
| AddMap
502502
| RemoveMap
503+
| AddMapLegacy
503504
[@@deriving rpc]

ocaml/database/db_cache_types.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,5 +169,6 @@ type structured_op_t =
169169
| RemoveSet
170170
| AddMap
171171
| RemoveMap
172+
| AddMapLegacy
172173
val structured_op_t_of_rpc: Rpc.t -> structured_op_t
173174
val rpc_of_structured_op_t: structured_op_t -> Rpc.t

ocaml/database/db_globs.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ let static_vdis_dir = ref "/etc/xensource/static-vdis"
5050
(* Note the following has an equivalent in the xapi layer *)
5151
let http_limit_max_rpc_size = 300 * 1024 (* 300K *)
5252

53+
(* add_to_map is idempotent *)
54+
let idempotent_map = ref false
55+
5356
(** Dynamic configurations to be read whenever xapi (re)start *)
5457

5558
let permanent_master_failure_retry_interval = ref 60.

ocaml/database/db_rpc_common_v1.ml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ let marshall_structured_op x =
7979
AddSet -> "addset"
8080
| RemoveSet -> "removeset"
8181
| AddMap -> "addmap"
82-
| RemoveMap -> "removemap" in
82+
| RemoveMap -> "removemap"
83+
| AddMapLegacy -> "addmap" (* Nb, we always use 'non-legacy' mode for remote access *)
84+
in
8385
XMLRPC.To.string str
8486
let unmarshall_structured_op xml =
8587
match (XMLRPC.From.string xml) with
@@ -311,4 +313,3 @@ let unmarshall_read_records_where_response xml =
311313
[ref_xml; rec_xml] -> (XMLRPC.From.string ref_xml, unmarshall_read_record_response rec_xml)
312314
| _ -> raise DB_remote_marshall_error)
313315
xml_refs_and_recs_list
314-

ocaml/database/db_rpc_common_v2.ml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ module Request = struct
3434
| Read_records_where of string * Db_filter_types.expr
3535
| Process_structured_field of (string * string) * string * string * string * Db_cache_types.structured_op_t
3636
[@@deriving rpc]
37+
38+
(* Make sure the slave only ever uses the idempotent version *)
39+
let rpc_of_t t =
40+
let t' =
41+
match t with
42+
| Process_structured_field (a,b,c,d,Db_cache_types.AddMapLegacy) ->
43+
Process_structured_field (a,b,c,d,Db_cache_types.AddMap)
44+
| x -> x
45+
in
46+
rpc_of_t t'
3747
end
3848

3949
module Response = struct

ocaml/idl/ocaml_backend/gen_db_actions.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ let db_action api : O.Module.t =
312312
(Escaping.escape_id full_name)
313313
Client._self
314314
| FromField(Add, { DT.ty = DT.Map(_, _); full_name = full_name }) ->
315-
Printf.sprintf "DB.process_structured_field __t (%s,%s) \"%s\" \"%s\" %s AddMap"
315+
Printf.sprintf "DB.process_structured_field __t (%s,%s) \"%s\" \"%s\" %s AddMapLegacy"
316316
Client._key Client._value
317317
(Escaping.escape_obj obj.DT.name)
318318
(Escaping.escape_id full_name)
@@ -472,4 +472,3 @@ let db_defaults api : O.Signature.t =
472472
{ O.Signature.name = _db_defaults;
473473
elements = List.map (fun x -> O.Signature.Module (obj x)) (Dm_api.objects_of_api api)
474474
}
475-

ocaml/xapi/test_db_lowlevel.ml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,38 @@ let test_db_get_all_records_race () =
4141
let tear_down () =
4242
Db_cache_impl.fist_delay_read_records_where := false
4343

44+
let test_idempotent_map () =
45+
Db_globs.idempotent_map := false;
46+
let __context = make_test_database () in
47+
let (vm_ref: API.ref_VM) = make_vm ~__context () in
48+
Db.VM.add_to_other_config ~__context ~self:vm_ref ~key:"test" ~value:"value";
49+
assert_raises (Db_exn.Duplicate_key ("VM","other_config",(Ref.string_of vm_ref),"test"))
50+
(fun () -> Db.VM.add_to_other_config ~__context ~self:vm_ref ~key:"test" ~value:"value");
51+
assert_raises (Db_exn.Duplicate_key ("VM","other_config",(Ref.string_of vm_ref),"test"))
52+
(fun () -> Db.VM.add_to_other_config ~__context ~self:vm_ref ~key:"test" ~value:"value2");
53+
54+
Db_globs.idempotent_map := true;
55+
let __context = make_test_database () in
56+
let (vm_ref: API.ref_VM) = make_vm ~__context () in
57+
Db.VM.add_to_other_config ~__context ~self:vm_ref ~key:"test" ~value:"value";
58+
assert_equal (Db.VM.add_to_other_config ~__context ~self:vm_ref ~key:"test" ~value:"value") ();
59+
assert_raises (Db_exn.Duplicate_key ("VM","other_config",(Ref.string_of vm_ref),"test"))
60+
(fun () -> Db.VM.add_to_other_config ~__context ~self:vm_ref ~key:"test" ~value:"value2");
61+
62+
Db_globs.idempotent_map := false
63+
64+
let test_slave_uses_nonlegacy_addmap () =
65+
let operation = Db_cache_types.AddMapLegacy in
66+
let operation' = Db_rpc_common_v1.marshall_structured_op operation |> Db_rpc_common_v1.unmarshall_structured_op in
67+
assert_equal operation' Db_cache_types.AddMap;
68+
let operationv2 = Db_rpc_common_v2.Request.Process_structured_field (("",""),"","","",Db_cache_types.AddMapLegacy) in
69+
let operationv2' = Db_rpc_common_v2.Request.(operationv2 |> rpc_of_t |> t_of_rpc) in
70+
assert_equal operationv2' (Db_rpc_common_v2.Request.Process_structured_field (("",""),"","","",Db_cache_types.AddMap))
71+
4472
let test =
4573
"test_db_lowlevel" >:::
4674
[
4775
"test_db_get_all_records_race" >:: (bracket id test_db_get_all_records_race tear_down);
76+
"test_db_idempotent_map" >:: test_idempotent_map;
77+
"test_slaves_use_nonlegacy_addmap" >:: test_slave_uses_nonlegacy_addmap;
4878
]

ocaml/xapi/xapi_globs.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,10 @@ let other_options = [
980980

981981
"modprobe_path", Arg.Set_string modprobe_path,
982982
(fun () -> !modprobe_path), "Location of the modprobe(8) command: should match $(which modprobe)";
983+
984+
"db_idempotent_map", Arg.Set Db_globs.idempotent_map,
985+
(fun () -> string_of_bool !Db_globs.idempotent_map), "True if the add_to_<map> API calls should be idempotent";
986+
983987
]
984988

985989
let all_options = options_of_xapi_globs_spec @ other_options

0 commit comments

Comments
 (0)