Skip to content

Commit d17d17f

Browse files
authored
Merge pull request xapi-project#3106 from lindig/pool-join-hnl
Backport: Add pool join check CP-23026: pool/host must have same "enforced" updates
2 parents 5fea716 + 132738c commit d17d17f

File tree

8 files changed

+117
-38
lines changed

8 files changed

+117
-38
lines changed

ocaml/client_records/records.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,7 @@ let pool_update_record rpc session_id update =
10321032
make_field ~name:"installation-size" ~get:(fun () -> Int64.to_string (x ()).API.pool_update_installation_size) ();
10331033
make_field ~name:"hosts" ~get:(fun () -> String.concat ", " (get_hosts ())) ~get_set:get_hosts ();
10341034
make_field ~name:"after-apply-guidance" ~get:(fun () -> String.concat ", " (after_apply_guidance ())) ~get_set:after_apply_guidance ();
1035+
make_field ~name:"enforce-homogeneity" ~get:(fun () -> string_of_bool (x ()).API.pool_update_enforce_homogeneity) ();
10351036
]}
10361037

10371038
let host_cpu_record rpc session_id host_cpu =

ocaml/idl/api_errors.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,9 @@ let pool_joining_host_must_have_physical_management_nic = "POOL_JOINING_HOST_MUS
306306
let pool_joining_external_auth_mismatch = "POOL_JOINING_EXTERNAL_AUTH_MISMATCH"
307307
let pool_joining_host_must_have_same_product_version = "POOL_JOINING_HOST_MUST_HAVE_SAME_PRODUCT_VERSION"
308308
let pool_joining_host_must_only_have_physical_pifs = "POOL_JOINING_HOST_MUST_ONLY_HAVE_PHYSICAL_PIFS"
309+
let pool_joining_host_must_have_same_api_version = "POOL_JOINING_HOST_MUST_HAVE_SAME_API_VERSION"
310+
let pool_joining_host_must_have_same_db_schema = "POOL_JOINING_HOST_MUST_HAVE_SAME_DB_SCHEMA"
311+
309312

310313
(*workload balancing*)
311314
let wlb_not_initialized = "WLB_NOT_INITIALIZED"

ocaml/idl/datamodel.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,10 @@ let _ =
879879
~doc:"The pool failed to disable the external authentication of at least one host." ();
880880
error Api_errors.pool_auth_disable_failed_permission_denied ["host";"message"]
881881
~doc:"The pool failed to disable the external authentication of at least one host." ();
882+
error Api_errors.pool_joining_host_must_have_same_api_version ["host_api_version";"master_api_version"]
883+
~doc:"The host joining the pool must have the same API version as the pool master." ();
884+
error Api_errors.pool_joining_host_must_have_same_db_schema ["host_db_schema";"master_db_schema"]
885+
~doc:"The host joining the pool must have the same database schema as the pool master." ();
882886

883887
(* External directory service *)
884888
error Api_errors.subject_cannot_be_resolved []
@@ -4128,6 +4132,13 @@ let pool_update =
41284132
field ~in_product_since:rel_ely ~default_value:(Some (VSet [])) ~in_oss_since:None ~qualifier:StaticRO ~ty:(Set pool_update_after_apply_guidance) "after_apply_guidance" "What the client should do after this update has been applied.";
41294133
field ~in_oss_since:None ~qualifier:StaticRO ~ty:(Ref _vdi) "vdi" "VDI the update was uploaded to";
41304134
field ~in_product_since:rel_ely ~in_oss_since:None ~qualifier:DynamicRO ~ty:(Set (Ref _host)) "hosts" "The hosts that have applied this update.";
4135+
field ~in_product_since:rel_honolulu
4136+
~default_value:(Some (VBool false))
4137+
~in_oss_since:None
4138+
~qualifier:StaticRO
4139+
~ty:Bool
4140+
"enforce_homogeneity"
4141+
"Flag - if true, all hosts in a pool must apply this update";
41314142
]
41324143
()
41334144

ocaml/idl/datamodel_types.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ let rel_indigo = "indigo"
5656
let rel_dundee = "dundee"
5757
let rel_dundee_plus = "dundee-plus"
5858
let rel_ely = "ely"
59+
let rel_honolulu = "honolulu"
5960

6061
let release_order =
6162
[ rel_rio
@@ -79,6 +80,7 @@ let release_order =
7980
; rel_dundee
8081
; rel_dundee_plus
8182
; rel_ely
83+
; rel_honolulu
8284
]
8385

8486
exception Unknown_release of string

ocaml/test/test_common.ml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,26 @@ let make_pvs_cache_storage ~__context ?(ref=Ref.make ()) ?(uuid=make_uuid ())
350350
~ref ~uuid ~host ~sR ~site ~size ~vDI;
351351
ref
352352

353-
let make_pool_update ~__context ?(ref=Ref.make ()) ?(uuid=make_uuid ())
354-
?(name_label="") ?(name_description="") ?(version="") ?(installation_size=0L) ?(key="")
355-
?(after_apply_guidance=[]) ?(vdi=Ref.null) () =
356-
let update_info = Xapi_pool_update.{uuid; name_label; name_description; version; key; installation_size; after_apply_guidance} in
353+
let make_pool_update ~__context
354+
?(ref=Ref.make ())
355+
?(uuid=make_uuid ())
356+
?(name_label="")
357+
?(name_description="")
358+
?(version="")
359+
?(installation_size=0L)
360+
?(key="")
361+
?(after_apply_guidance=[])
362+
?(enforce_homogeneity=false)
363+
?(vdi=Ref.null) () =
364+
let update_info = Xapi_pool_update.
365+
{ uuid
366+
; name_label
367+
; name_description
368+
; version
369+
; key
370+
; installation_size
371+
; after_apply_guidance
372+
; enforce_homogeneity
373+
} in
357374
Xapi_pool_update.create_update_record ~__context ~update:ref ~update_info ~vdi;
358375
ref

ocaml/xapi/xapi_globs.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ let _xapi_major = "xapi_major"
123123
let _xapi_minor = "xapi_minor"
124124
let _export_vsn = "export_vsn"
125125
let _dbv = "dbv"
126+
let _db_schema = "db_schema"
127+
126128

127129
(* When comparing two host versions, always treat a host that has platform_version defined as newer
128130
* than any host that does not have platform_version defined.

ocaml/xapi/xapi_pool.ml

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -111,32 +111,68 @@ let pre_join_checks ~__context ~rpc ~session_id ~force =
111111
raise (Api_errors.Server_error (code, ["The pool uses v6d. Pool edition list = " ^ pool_edn_list_str]))
112112
in
113113

114-
(* CA-73264 Applied patches must match *)
115-
let assert_applied_patches_match () =
116-
let get_patches patches get_pool_patch get_uuid =
117-
let patch_refs = List.map (fun x -> get_pool_patch ~self:x) patches in
118-
let patch_uuids = List.map (fun x -> get_uuid ~self:x) patch_refs in
119-
patch_uuids in
120-
let pool_patches = get_patches
121-
(Client.Host.get_patches ~rpc ~session_id ~self:(get_master ~rpc ~session_id))
122-
(Client.Host_patch.get_pool_patch ~rpc ~session_id)
123-
(Client.Pool_patch.get_uuid ~rpc ~session_id) in
124-
let host_patches = get_patches
125-
(Db.Host.get_patches ~__context ~self:(Helpers.get_localhost ~__context))
126-
(Db.Host_patch.get_pool_patch ~__context) (Db.Pool_patch.get_uuid ~__context) in
127-
let string_of_patches ps = (String.concat " " (List.map (fun patch -> patch) ps)) in
128-
let diff = (List.set_difference host_patches pool_patches) @
129-
(List.set_difference pool_patches host_patches)in
130-
if (List.length diff > 0) then begin
131-
error "Pool.join failed because of patches mismatch";
132-
error "Remote has %s" (string_of_patches pool_patches);
133-
error "Local has %s" (string_of_patches host_patches);
134-
raise (Api_errors.Server_error(Api_errors.pool_hosts_not_homogeneous,
135-
[(Printf.sprintf "Patches applied differ: Remote has %s -- Local has %s"
136-
(string_of_patches pool_patches) (string_of_patches host_patches))]))
114+
let assert_api_version_matches () =
115+
let master = get_master rpc session_id in
116+
let candidate_slave = Helpers.get_localhost ~__context in
117+
let master_major = Client.Host.get_API_version_major ~rpc ~session_id ~self:master in
118+
let master_minor = Client.Host.get_API_version_minor ~rpc ~session_id ~self:master in
119+
let slave_major = Db.Host.get_API_version_major ~__context ~self:candidate_slave in
120+
let slave_minor = Db.Host.get_API_version_minor ~__context ~self:candidate_slave in
121+
if master_major <> slave_major || master_minor <> slave_minor then
122+
begin
123+
error "The joining host's API version is %Ld.%Ld while the master's is %Ld.%Ld"
124+
slave_major slave_minor master_major master_minor;
125+
raise (Api_errors.Server_error(Api_errors.pool_joining_host_must_have_same_api_version,
126+
[Printf.sprintf "%Ld.%Ld" slave_major slave_minor; Printf.sprintf "%Ld.%Ld" master_major master_minor;]))
127+
end
128+
in
129+
130+
let assert_db_schema_matches () =
131+
let master = get_master rpc session_id in
132+
let candidate_slave = Helpers.get_localhost ~__context in
133+
let master_sw_version = Client.Host.get_software_version ~rpc ~session_id ~self:master in
134+
let slave_sw_version = Db.Host.get_software_version ~__context ~self:candidate_slave in
135+
let master_db_schema = try List.assoc Xapi_globs._db_schema master_sw_version with _ -> "" in
136+
let slave_db_schema = try List.assoc Xapi_globs._db_schema slave_sw_version with _ -> "" in
137+
if master_db_schema = "" || slave_db_schema = "" || master_db_schema <> slave_db_schema then
138+
begin
139+
error "The joining host's database schema is %s; the master's is %s"
140+
slave_db_schema master_db_schema;
141+
raise (Api_errors.Server_error(Api_errors.pool_joining_host_must_have_same_db_schema,
142+
[slave_db_schema; master_db_schema]))
137143
end
138144
in
139145

146+
let assert_homogeneous_updates () =
147+
let module S = Helpers.StringSet in
148+
let local_host = Helpers.get_localhost ~__context in
149+
let local_uuid = Db.Host.get_uuid ~__context ~self:local_host in
150+
let updates_on ~rpc ~session_id host =
151+
Client.Host.get_updates ~rpc ~session_id ~self:host
152+
|> List.map (fun self -> Client.Pool_update.get_record ~rpc ~session_id ~self)
153+
|> List.filter (fun upd -> upd.API.pool_update_enforce_homogeneity = true)
154+
|> List.map (fun upd -> upd.API.pool_update_uuid)
155+
|> S.of_list in
156+
let local_updates =
157+
Helpers.call_api_functions ~__context (fun rpc session_id ->
158+
updates_on ~rpc ~session_id local_host) in
159+
(* iterate over all pool hosts and compare patches to local host *)
160+
Client.Host.get_all rpc session_id |> List.iter (fun pool_host ->
161+
let remote_updates = updates_on rpc session_id pool_host in
162+
if not (S.equal local_updates remote_updates) then begin
163+
let remote_uuid = Client.Host.get_uuid rpc session_id pool_host in
164+
let diff xs ys = S.diff xs ys |> S.elements |> String.concat "," in
165+
let reason = [remote_uuid] in
166+
error
167+
"Pool join: Updates differ. Only on pool host %s: {%s} -- only on local host %s: {%s}"
168+
remote_uuid
169+
(diff remote_updates local_updates)
170+
local_uuid
171+
(diff local_updates remote_updates);
172+
raise Api_errors.(Server_error(pool_hosts_not_homogeneous,reason))
173+
end)
174+
in
175+
140176
(* CP-700: Restrict pool.join if AD configuration of slave-to-be does not match *)
141177
(* that of master of pool-to-join *)
142178
let assert_external_auth_matches () =
@@ -347,7 +383,10 @@ let pre_join_checks ~__context ~rpc ~session_id ~force =
347383
assert_external_auth_matches ();
348384
assert_restrictions_match ();
349385
assert_homogeneous_vswitch_configuration ();
350-
assert_applied_patches_match ();
386+
(* CA-247399: check first the API version and then the database schema *)
387+
assert_api_version_matches ();
388+
assert_db_schema_matches ();
389+
assert_homogeneous_updates ();
351390
assert_homogeneous_primary_address_type ()
352391

353392
let rec create_or_get_host_on_master __context rpc session_id (host_ref, host) : API.ref_host =

ocaml/xapi/xapi_pool_update.ml

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type update_info = {
4444
key: string;
4545
installation_size: int64;
4646
after_apply_guidance: API.after_apply_guidance list;
47+
enforce_homogeneity: bool; (* true = all hosts in a pool must have this update *)
4748
}
4849

4950
(** Mount a filesystem somewhere, with optional type *)
@@ -258,6 +259,9 @@ let parse_update_info xml =
258259
with
259260
| _ -> []
260261
in
262+
let enforce_homogeneity =
263+
Vm_platform.is_true ~key:"enforce-homogeneity" ~platformdata:attr ~default:false
264+
in
261265
let is_name_description_node = function
262266
| Xml.Element ("name-description", _, _) -> true
263267
| _ -> false
@@ -266,16 +270,15 @@ let parse_update_info xml =
266270
| Xml.Element("name-description", _, [ Xml.PCData s ]) -> s
267271
| _ -> raise (Api_errors.Server_error(Api_errors.invalid_update, ["missing <name-description> in update.xml"]))
268272
in
269-
let update_info = {
270-
uuid = uuid;
271-
name_label = name_label;
272-
name_description = name_description;
273-
version = version;
274-
key = Filename.basename key;
275-
installation_size = installation_size;
276-
after_apply_guidance = guidance;
277-
} in
278-
update_info
273+
{ uuid
274+
; name_label
275+
; name_description
276+
; version
277+
; key = Filename.basename key
278+
; installation_size
279+
; after_apply_guidance = guidance
280+
; enforce_homogeneity
281+
}
279282
| _ -> raise (Api_errors.Server_error(Api_errors.invalid_update, ["missing <update> in update.xml"]))
280283

281284
let extract_applied_update_info applied_uuid =
@@ -368,6 +371,7 @@ let create_update_record ~__context ~update ~update_info ~vdi =
368371
~key:update_info.key
369372
~after_apply_guidance:update_info.after_apply_guidance
370373
~vdi:vdi
374+
~enforce_homogeneity:update_info.enforce_homogeneity
371375

372376
let introduce ~__context ~vdi =
373377
ignore(Unixext.mkdir_safe Xapi_globs.host_update_dir 0o755);

0 commit comments

Comments
 (0)