Skip to content

Commit 5b121e2

Browse files
authored
CA-422071: guard against losing Host field settings on pool join (#6799)
This only looks at newly added fields (those with an empty `lifecycle`), and requires them to be present in `Host.create_params`. This ensures that we get a compile error, and are forced to propagate it during pool join. Otherwise newly added fields seem to keep reintroducing this bug with every newly added feature (e.g. the pending NTP feature branch has this bug on most of its fields). So far I only found this bug on the update guidance fields. There are more bugs on other pre-existing fields in older releases, but those are skipped by the unit test (there are too many, `logging`, `iscsi_iqn`, etc.). We do want to eventually fix those, but it'll require a lot more testing, so will be done separately (also some of them are actually overwritten in dbsync_slave). There are a lot more properties we could check in the unit test (e.g. that all newly added parameters have defaults for backwards compatibility, that the doc and type matches the field, etc.). Although eventually I'd probably want to entirely auto-generate `create_params`, but we'll need to see how to do that to also take into account what dbsync_slave already does.
2 parents b0eaef3 + bd8c79a commit 5b121e2

File tree

10 files changed

+100
-23
lines changed

10 files changed

+100
-23
lines changed

ocaml/idl/datamodel_host.ml

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,24 @@ let host_numa_affinity_policy =
12261226
]
12271227
)
12281228

1229+
let latest_synced_updates_applied_state =
1230+
Enum
1231+
( "latest_synced_updates_applied_state"
1232+
, [
1233+
( "yes"
1234+
, "The host is up to date with the latest updates synced from remote \
1235+
CDN"
1236+
)
1237+
; ( "no"
1238+
, "The host is outdated with the latest updates synced from remote CDN"
1239+
)
1240+
; ( "unknown"
1241+
, "If the host is up to date with the latest updates synced from \
1242+
remote CDN is unknown"
1243+
)
1244+
]
1245+
)
1246+
12291247
let create_params =
12301248
[
12311249
{
@@ -1430,6 +1448,36 @@ let create_params =
14301448
; param_release= numbered_release "25.39.0-next"
14311449
; param_default= Some (VEnum "default_policy")
14321450
}
1451+
; {
1452+
param_type= latest_synced_updates_applied_state
1453+
; param_name= "latest_synced_updates_applied"
1454+
; param_doc=
1455+
"Default as 'unknown', 'yes' if the host is up to date with updates \
1456+
synced from remote CDN, otherwise 'no'"
1457+
; param_release= numbered_release "25.39.0-next"
1458+
; param_default= Some (VSet [])
1459+
}
1460+
; {
1461+
param_type= Set update_guidances
1462+
; param_name= "pending_guidances_full"
1463+
; param_doc=
1464+
"The set of pending full guidances after applying updates, which a \
1465+
user should follow to make some updates, e.g. specific hardware \
1466+
drivers or CPU features, fully effective, but the 'average user' \
1467+
doesn't need to"
1468+
; param_release= numbered_release "25.39.0-next"
1469+
; param_default= Some (VSet [])
1470+
}
1471+
; {
1472+
param_type= Set update_guidances
1473+
; param_name= "pending_guidances_recommended"
1474+
; param_doc=
1475+
"The set of pending recommended guidances after applying updates, \
1476+
which most users should follow to make the updates effective, but if \
1477+
not followed, will not cause a failure"
1478+
; param_release= numbered_release "25.39.0-next"
1479+
; param_default= Some (VSet [])
1480+
}
14331481
]
14341482

14351483
let create =
@@ -2542,24 +2590,6 @@ let update_firewalld_service_status =
25422590
status."
25432591
~allowed_roles:_R_POOL_OP ()
25442592

2545-
let latest_synced_updates_applied_state =
2546-
Enum
2547-
( "latest_synced_updates_applied_state"
2548-
, [
2549-
( "yes"
2550-
, "The host is up to date with the latest updates synced from remote \
2551-
CDN"
2552-
)
2553-
; ( "no"
2554-
, "The host is outdated with the latest updates synced from remote CDN"
2555-
)
2556-
; ( "unknown"
2557-
, "If the host is up to date with the latest updates synced from \
2558-
remote CDN is unknown"
2559-
)
2560-
]
2561-
)
2562-
25632593
let get_tracked_user_agents =
25642594
call ~name:"get_tracked_user_agents" ~lifecycle:[]
25652595
~doc:

ocaml/idl/dune

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@
6464
)
6565

6666
(tests
67-
(names schematest test_datetimes)
67+
(names schematest test_datetimes test_host)
6868
(modes exe)
69-
(modules schematest test_datetimes)
69+
(modules schematest test_datetimes test_host)
7070
(libraries
7171
astring
7272
rpclib.core

ocaml/idl/test_host.ml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module DT = Datamodel_types
2+
module FieldSet = Astring.String.Set
3+
4+
let recent_field (f : DT.field) = f.lifecycle.transitions = []
5+
6+
let rec field_full_names = function
7+
| DT.Field f ->
8+
if recent_field f then
9+
f.full_name |> String.concat "_" |> Seq.return
10+
else
11+
Seq.empty
12+
| DT.Namespace (_, xs) ->
13+
xs |> List.to_seq |> Seq.concat_map field_full_names
14+
15+
let () =
16+
let create_params =
17+
Datamodel_host.create_params
18+
|> List.map (fun p -> p.DT.param_name)
19+
|> FieldSet.of_list
20+
and fields =
21+
Datamodel_host.t.contents
22+
|> List.to_seq
23+
|> Seq.concat_map field_full_names
24+
|> FieldSet.of_seq
25+
in
26+
let missing_in_create_params = FieldSet.diff fields create_params in
27+
if not (FieldSet.is_empty missing_in_create_params) then (
28+
Format.eprintf "Missing fields in create_params: %a@." FieldSet.dump
29+
missing_in_create_params ;
30+
exit 1
31+
)

ocaml/idl/test_host.mli

Whitespace-only changes.

ocaml/tests/common/test_common.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ let make_host ~__context ?(uuid = make_uuid ()) ?(name_label = "host")
185185
~console_idle_timeout ~ssh_auto_mode ~secure_boot
186186
~software_version:(Xapi_globs.software_version ())
187187
~https_only ~numa_affinity_policy:`default_policy
188+
~latest_synced_updates_applied:`unknown ~pending_guidances_full:[]
189+
~pending_guidances_recommended:[]
188190
in
189191
Db.Host.set_cpu_info ~__context ~self:host ~value:default_cpu_info ;
190192
host

ocaml/tests/test_host.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ let add_host __context name =
2828
~console_idle_timeout:0L ~ssh_auto_mode:false ~secure_boot:false
2929
~software_version:(Xapi_globs.software_version ())
3030
~https_only:false ~numa_affinity_policy:`default_policy
31+
~latest_synced_updates_applied:`unknown ~pending_guidances_full:[]
32+
~pending_guidances_recommended:[]
3133
)
3234

3335
(* Creates an unlicensed pool with the maximum number of hosts *)

ocaml/xapi/dbsync_slave.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ let create_localhost ~__context info =
6767
~ssh_auto_mode:!Xapi_globs.ssh_auto_mode_default
6868
~secure_boot:false ~software_version:[]
6969
~https_only:!Xapi_globs.https_only ~numa_affinity_policy:`default_policy
70+
~latest_synced_updates_applied:`unknown ~pending_guidances_full:[]
71+
~pending_guidances_recommended:[]
7072
in
7173
()
7274

ocaml/xapi/xapi_host.ml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,9 @@ let create ~__context ~uuid ~name_label ~name_description:_ ~hostname ~address
10291029
~license_params ~edition ~license_server ~local_cache_sr ~chipset_info
10301030
~ssl_legacy:_ ~last_software_update ~last_update_hash ~ssh_enabled
10311031
~ssh_enabled_timeout ~ssh_expiry ~console_idle_timeout ~ssh_auto_mode
1032-
~secure_boot ~software_version ~https_only ~numa_affinity_policy =
1032+
~secure_boot ~software_version ~https_only ~numa_affinity_policy
1033+
~latest_synced_updates_applied ~pending_guidances_full
1034+
~pending_guidances_recommended =
10331035
(* fail-safe. We already test this on the joining host, but it's racy, so multiple concurrent
10341036
pool-join might succeed. Note: we do it in this order to avoid a problem checking restrictions during
10351037
the initial setup of the database *)
@@ -1090,8 +1092,8 @@ let create ~__context ~uuid ~name_label ~name_description:_ ~hostname ~address
10901092
~control_domain:Ref.null ~updates_requiring_reboot:[] ~iscsi_iqn:""
10911093
~multipathing:false ~uefi_certificates:"" ~editions:[] ~pending_guidances:[]
10921094
~tls_verification_enabled ~last_software_update ~last_update_hash
1093-
~recommended_guidances:[] ~latest_synced_updates_applied:`unknown
1094-
~pending_guidances_recommended:[] ~pending_guidances_full:[] ~ssh_enabled
1095+
~recommended_guidances:[] ~latest_synced_updates_applied
1096+
~pending_guidances_recommended ~pending_guidances_full ~ssh_enabled
10951097
~ssh_enabled_timeout ~ssh_expiry ~console_idle_timeout ~ssh_auto_mode
10961098
~secure_boot ;
10971099
(* If the host we're creating is us, make sure its set to live *)

ocaml/xapi/xapi_host.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ val create :
140140
-> software_version:(string * string) list
141141
-> https_only:bool
142142
-> numa_affinity_policy:API.host_numa_affinity_policy
143+
-> latest_synced_updates_applied:API.latest_synced_updates_applied_state
144+
-> pending_guidances_full:API.update_guidances_set
145+
-> pending_guidances_recommended:API.update_guidances_set
143146
-> [`host] Ref.t
144147

145148
val destroy : __context:Context.t -> self:API.ref_host -> unit

ocaml/xapi/xapi_pool.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,11 @@ let rec create_or_get_host_on_master __context rpc session_id (host_ref, host) :
10631063
~software_version:host.API.host_software_version
10641064
~https_only:host.API.host_https_only
10651065
~numa_affinity_policy:host.API.host_numa_affinity_policy
1066+
~latest_synced_updates_applied:
1067+
host.API.host_latest_synced_updates_applied
1068+
~pending_guidances_full:host.API.host_pending_guidances_full
1069+
~pending_guidances_recommended:
1070+
host.API.host_pending_guidances_recommended
10661071
in
10671072
(* Copy other-config into newly created host record: *)
10681073
no_exn

0 commit comments

Comments
 (0)