diff --git a/ocaml/idl/datamodel.ml b/ocaml/idl/datamodel.ml index 5fcf06c84df..15f1c4c66c6 100644 --- a/ocaml/idl/datamodel.ml +++ b/ocaml/idl/datamodel.ml @@ -7966,6 +7966,7 @@ let emergency_calls = ; (Datamodel_host.t, Datamodel_host.emergency_reset_server_certificate) ; (Datamodel_host.t, Datamodel_host.emergency_disable_tls_verification) ; (Datamodel_host.t, Datamodel_host.emergency_reenable_tls_verification) + ; (Datamodel_host.t, Datamodel_host.emergency_clear_mandatory_guidance) ] (** Whitelist of calls that will not get forwarded from the slave to master via the unix domain socket *) diff --git a/ocaml/idl/datamodel_common.ml b/ocaml/idl/datamodel_common.ml index 7e4aa8e4fe7..709cb5eb059 100644 --- a/ocaml/idl/datamodel_common.ml +++ b/ocaml/idl/datamodel_common.ml @@ -10,7 +10,7 @@ open Datamodel_roles to leave a gap for potential hotfixes needing to increment the schema version.*) let schema_major_vsn = 5 -let schema_minor_vsn = 774 +let schema_minor_vsn = 775 (* Historical schema versions just in case this is useful later *) let rio_schema_major_vsn = 5 @@ -134,6 +134,10 @@ let yangtze_release_schema_major_vsn = 5 let yangtze_release_schema_minor_vsn = 602 +let nile_release_schema_major_vsn = 5 + +let nile_release_schema_minor_vsn = 775 + (* List of tech-preview releases. Fields in these releases are not guaranteed to be retained when * upgrading to a full release. *) let tech_preview_releases = @@ -151,7 +155,7 @@ let tech_preview_releases = * but there are exceptions: please consult the XenCenter maintainers if in doubt. *) let api_version_major = 2L -let api_version_minor = 20L +let api_version_minor = 21L let api_version_string = Printf.sprintf "%Ld.%Ld" api_version_major api_version_minor @@ -316,6 +320,14 @@ let update_guidances = , "Indicates the updated host should reboot as soon as possible since \ one or more livepatch(es) failed to be applied." ) + ; ( "reboot_host_on_kernel_livepatch_failure" + , "Indicates the updated host should reboot as soon as possible since \ + one or more kernel livepatch(es) failed to be applied." + ) + ; ( "reboot_host_on_xen_livepatch_failure" + , "Indicates the updated host should reboot as soon as possible since \ + one or more xen livepatch(es) failed to be applied." + ) ; ( "restart_toolstack" , "Indicates the Toolstack running on the updated host should restart \ as soon as possible" @@ -324,6 +336,7 @@ let update_guidances = , "Indicates the device model of a running VM should restart as soon \ as possible" ) + ; ("restart_vm", "Indicates the VM should restart as soon as possible") ] ) diff --git a/ocaml/idl/datamodel_errors.ml b/ocaml/idl/datamodel_errors.ml index 2aa67691774..0a0166dfe93 100644 --- a/ocaml/idl/datamodel_errors.ml +++ b/ocaml/idl/datamodel_errors.ml @@ -1942,10 +1942,6 @@ let _ = ~doc:"The repository domain allowlist has some invalid domains." () ; error Api_errors.apply_livepatch_failed ["livepatch"] ~doc:"Failed to apply a livepatch." () ; - error Api_errors.updates_require_recommended_guidance ["recommended_guidance"] - ~doc:"Requires recommended guidance after applying updates." () ; - error Api_errors.update_guidance_changed ["guidance"] - ~doc:"Guidance for the update has changed" () ; error Api_errors.vtpm_max_amount_reached ["amount"] ~doc:"The VM cannot be associated with more VTPMs." () ; @@ -1963,6 +1959,15 @@ let _ = error Api_errors.no_repositories_configured [] ~doc:"No update repositories have been configured." () ; + error Api_errors.host_pending_mandatory_guidances_not_empty ["host"] + ~doc: + "Operation could not be performed on the host because there is pending \ + mandatory update guidance on it." + () ; + + error Api_errors.host_evacuation_is_required ["host"] + ~doc:"Host evacuation is required before applying updates." () ; + message (fst Api_messages.ha_pool_overcommitted) ~doc: diff --git a/ocaml/idl/datamodel_host.ml b/ocaml/idl/datamodel_host.ml index 218afa63fd2..6c7895ec901 100644 --- a/ocaml/idl/datamodel_host.ml +++ b/ocaml/idl/datamodel_host.ml @@ -1263,6 +1263,7 @@ let operations = ; ("vm_resume", "This host is resuming a VM") ; ("vm_migrate", "This host is the migration target of a VM") ; ("apply_updates", "Indicates this host is being updated") + ; ("enable", "Indicates this host is in the process of enabling") ] ) @@ -1809,6 +1810,12 @@ let apply_recommended_guidances = ] ~allowed_roles:_R_POOL_OP () +let emergency_clear_mandatory_guidance = + call ~flags:[`Session] ~name:"emergency_clear_mandatory_guidance" + ~lifecycle:[] ~in_oss_since:None ~params:[] + ~doc:"Clear the pending mandatory guidance on this host" + ~allowed_roles:_R_LOCAL_ROOT_ONLY () + let latest_synced_updates_applied_state = Enum ( "latest_synced_updates_applied_state" @@ -1963,6 +1970,7 @@ let t = ; copy_primary_host_certs ; set_https_only ; apply_recommended_guidances + ; emergency_clear_mandatory_guidance ] ~contents: ([ @@ -2173,7 +2181,8 @@ let t = ; field ~qualifier:DynamicRO ~in_product_since:"1.303.0" ~ty:(Set update_guidances) "pending_guidances" ~default_value:(Some (VSet [])) - "The set of pending guidances after applying updates" + "The set of pending mandatory guidances after applying updates, \ + which must be applied, as otherwise there may be e.g. VM failures" ; field ~qualifier:DynamicRO ~in_product_since:"1.313.0" ~ty:Bool "tls_verification_enabled" ~default_value:(Some (VBool false)) "True if this host has TLS verifcation enabled" @@ -2199,6 +2208,21 @@ let t = "numa_affinity_policy" ~default_value:(Some (VEnum "default_policy")) "NUMA-aware VM memory and vCPU placement policy" + ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:(Set update_guidances) + "pending_guidances_recommended" ~default_value:(Some (VSet [])) + "The set of pending recommended guidances after applying updates, \ + which most users should follow to make the updates effective, but \ + if not followed, will not cause a failure" + ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:(Set update_guidances) + "pending_guidances_full" ~default_value:(Some (VSet [])) + "The set of pending full guidances after applying updates, which a \ + user should follow to make some updates, e.g. specific hardware \ + drivers or CPU features, fully effective, but the 'average user' \ + doesn't need to" + ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:String + ~default_value:(Some (VString "")) "last_update_hash" + "The SHA256 checksum of updateinfo of the most recently applied \ + update on the host" ] ) () diff --git a/ocaml/idl/datamodel_lifecycle.ml b/ocaml/idl/datamodel_lifecycle.ml index 11abd8f9c4b..28ff931ec59 100644 --- a/ocaml/idl/datamodel_lifecycle.ml +++ b/ocaml/idl/datamodel_lifecycle.ml @@ -39,6 +39,12 @@ let prototyped_of_field = function Some "22.26.0" | "VTPM", "persistence_backend" -> Some "22.26.0" + | "host", "last_update_hash" -> + Some "24.10.0" + | "host", "pending_guidances_full" -> + Some "24.10.0" + | "host", "pending_guidances_recommended" -> + Some "24.10.0" | "host", "numa_affinity_policy" -> Some "24.0.0" | "host", "latest_synced_updates_applied" -> @@ -49,6 +55,10 @@ let prototyped_of_field = function Some "22.27.0" | "host", "last_software_update" -> Some "22.20.0" + | "VM", "pending_guidances_full" -> + Some "24.10.0" + | "VM", "pending_guidances_recommended" -> + Some "24.10.0" | "VM", "recommended_guidances" -> Some "23.18.0" | "VM", "actions__after_softreboot" -> @@ -109,6 +119,8 @@ let prototyped_of_message = function Some "22.26.0" | "VTPM", "create" -> Some "22.26.0" + | "host", "emergency_clear_mandatory_guidance" -> + Some "24.10.0" | "host", "apply_recommended_guidances" -> Some "23.18.0" | "host", "set_https_only" -> diff --git a/ocaml/idl/datamodel_types.ml b/ocaml/idl/datamodel_types.ml index 2feaf559c32..364eafb42db 100644 --- a/ocaml/idl/datamodel_types.ml +++ b/ocaml/idl/datamodel_types.ml @@ -97,6 +97,10 @@ let rel_stockholm = "stockholm" let rel_stockholm_psr = "stockholm_psr" +let rel_nile_preview = "nile-preview" + +let rel_nile = "nile" + type api_release = { code_name: string option ; version_major: int @@ -335,12 +339,19 @@ let release_order_full = ; release_date= Some "November 2020" } ; { - code_name= Some "nile-preview" + code_name= Some rel_nile_preview ; version_major= 2 ; version_minor= 20 ; branding= "XenServer 8 Preview" ; release_date= Some "August 2023" } + ; { + code_name= Some rel_nile + ; version_major= 2 + ; version_minor= 21 + ; branding= "XenServer 8" + ; release_date= None + } ] (* When you add a new release, use the version number of the latest release, "Unreleased" for the branding, and Some "" for the release date, until the actual values are finalised. *) diff --git a/ocaml/idl/datamodel_vm.ml b/ocaml/idl/datamodel_vm.ml index ec9a83f84c1..c1a6b9a7d9c 100644 --- a/ocaml/idl/datamodel_vm.ml +++ b/ocaml/idl/datamodel_vm.ml @@ -1687,8 +1687,9 @@ let set_NVRAM_EFI_variables = ~hide_from_docs:true ~allowed_roles:_R_LOCAL_ROOT_ONLY () let restart_device_models = - call ~flags:[`Session] ~name:"restart_device_models" ~lifecycle:[] + call ~name:"restart_device_models" ~lifecycle:[] ~params:[(Ref _vm, "self", "The VM")] + ~doc:"Restart device models of the VM" ~errs: [ Api_errors.vm_bad_power_state @@ -2155,12 +2156,24 @@ let t = ; field ~qualifier:DynamicRO ~in_product_since:"1.303.0" ~ty:(Set update_guidances) "pending_guidances" ~default_value:(Some (VSet [])) - "The set of pending guidances after applying updates" + "The set of pending mandatory guidances after applying updates, \ + which must be applied, as otherwise there may be e.g. VM failures" ; field ~qualifier:DynamicRO ~internal_only:true ~lifecycle:[(Prototyped, "23.18.0", ""); (Removed, "23.24.0", "")] ~ty:(Set update_guidances) "recommended_guidances" ~default_value:(Some (VSet [])) "The set of recommended guidances after applying updates" + ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:(Set update_guidances) + "pending_guidances_recommended" ~default_value:(Some (VSet [])) + "The set of pending recommended guidances after applying updates, \ + which most users should follow to make the updates effective, but \ + if not followed, will not cause a failure" + ; field ~qualifier:DynamicRO ~lifecycle:[] ~ty:(Set update_guidances) + "pending_guidances_full" ~default_value:(Some (VSet [])) + "The set of pending full guidances after applying updates, which a \ + user should follow to make some updates, e.g. specific hardware \ + drivers or CPU features, fully effective, but the 'average user' \ + doesn't need to" ] ) () diff --git a/ocaml/idl/schematest.ml b/ocaml/idl/schematest.ml index 133e21a1e9c..c8e5972c9a6 100644 --- a/ocaml/idl/schematest.ml +++ b/ocaml/idl/schematest.ml @@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex (* BEWARE: if this changes, check that schema has been bumped accordingly in ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *) -let last_known_schema_hash = "a99342e7a24557948df221c8da46ae71" +let last_known_schema_hash = "186131ad48f40dff30246e8e0c0dbf0a" let current_schema_hash : string = let open Datamodel_types in diff --git a/ocaml/tests/common/test_common.ml b/ocaml/tests/common/test_common.ml index 111ea8723e5..786a6eaca61 100644 --- a/ocaml/tests/common/test_common.ml +++ b/ocaml/tests/common/test_common.ml @@ -210,7 +210,9 @@ let make_host2 ~__context ?(ref = Ref.make ()) ?(uuid = make_uuid ()) ~multipathing:false ~uefi_certificates:"" ~editions:[] ~pending_guidances:[] ~tls_verification_enabled ~numa_affinity_policy:`default_policy ~last_software_update:(Xapi_host.get_servertime ~__context ~host:ref) - ~recommended_guidances:[] ~latest_synced_updates_applied:`unknown ; + ~recommended_guidances:[] ~latest_synced_updates_applied:`unknown + ~pending_guidances_recommended:[] ~pending_guidances_full:[] + ~last_update_hash:"" ; ref let make_pif ~__context ~network ~host ?(device = "eth0") diff --git a/ocaml/tests/test_repository_helpers.ml b/ocaml/tests/test_repository_helpers.ml index 197852013e4..dbb5b7f1a42 100644 --- a/ocaml/tests/test_repository_helpers.ml +++ b/ocaml/tests/test_repository_helpers.ml @@ -187,118 +187,6 @@ module UpdateOfJsonTest = Generic.MakeStateless (struct ] end) -module GuidanceSetAssertValidGuidanceTest = Generic.MakeStateless (struct - module Io = struct - type input_t = Guidance.t list - - type output_t = (unit, exn) result - - let string_of_input_t l = - Fmt.(str "%a" Dump.(list string)) (List.map Guidance.to_string l) - - let string_of_output_t = - Fmt.(str "%a" Dump.(result ~ok:(any "()") ~error:exn)) - end - - let transform input = - try Ok (GuidanceSet.assert_valid_guidances input) with e -> Error e - - let tests = - let open Guidance in - `QuickAndAutoDocumented - [ - ([], Ok ()) - ; ([RebootHost], Ok ()) - ; ([RestartToolstack], Ok ()) - ; ([RestartDeviceModel], Ok ()) - ; ([EvacuateHost], Ok ()) - ; ([EvacuateHost; RestartToolstack], Ok ()) - ; ([RestartDeviceModel; RestartToolstack], Ok ()) - ; ( [RestartDeviceModel; EvacuateHost] - , Error - Api_errors.( - Server_error - ( internal_error - , [GuidanceSet.error_msg [RestartDeviceModel; EvacuateHost]] - ) - ) - ) - ; ( [EvacuateHost; RestartToolstack; RestartDeviceModel] - , Error - Api_errors.( - Server_error - ( internal_error - , [ - GuidanceSet.error_msg - [EvacuateHost; RestartToolstack; RestartDeviceModel] - ] - ) - ) - ) - ; ( [RebootHost; RestartToolstack] - , Error - Api_errors.( - Server_error - ( internal_error - , [GuidanceSet.error_msg [RebootHost; RestartToolstack]] - ) - ) - ) - ; ( [RebootHost; RestartDeviceModel] - , Error - Api_errors.( - Server_error - ( internal_error - , [GuidanceSet.error_msg [RebootHost; RestartDeviceModel]] - ) - ) - ) - ; ( [RebootHost; EvacuateHost] - , Error - Api_errors.( - Server_error - ( internal_error - , [GuidanceSet.error_msg [RebootHost; EvacuateHost]] - ) - ) - ) - ] -end) - -let fields_of_updateinfo = - Fmt.Dump. - [ - field "id" (fun (r : UpdateInfo.t) -> r.id) string - ; field "summary" (fun (r : UpdateInfo.t) -> r.summary) string - ; field "description" (fun (r : UpdateInfo.t) -> r.description) string - ; field "rec_guidance" - (fun (r : UpdateInfo.t) -> UpdateInfo.guidance_to_string r.rec_guidance) - string - ; field "abs_guidance" - (fun (r : UpdateInfo.t) -> UpdateInfo.guidance_to_string r.abs_guidance) - string - ; field "guidance_applicabilities" - (fun (r : UpdateInfo.t) -> - List.map Applicability.to_string r.guidance_applicabilities - ) - (list string) - ; field "spec_info" (fun (r : UpdateInfo.t) -> r.spec_info) string - ; field "url" (fun (r : UpdateInfo.t) -> r.url) string - ; field "update_type" (fun (r : UpdateInfo.t) -> r.update_type) string - ; field "livepatch_guidance" - (fun (r : UpdateInfo.t) -> - UpdateInfo.guidance_to_string r.livepatch_guidance - ) - string - ; field "livepatches" - (fun (r : UpdateInfo.t) -> - List.map - (fun x -> x |> LivePatch.to_json |> Yojson.Basic.pretty_to_string) - r.livepatches - ) - (list string) - ] - module AssertUrlIsValid = Generic.MakeStateless (struct module Io = struct type input_t = string * string list @@ -520,10 +408,10 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct updates_info: (string * UpdateInfo.t) list ; update: Update.t ; upd_ids_of_livepatches: string list - ; upd_ids_of_failed_livepatches: string list + ; kind: Guidance.kind } - type output_t = Guidance.t option + type output_t = Guidance.t list let fields_of_input = Fmt.Dump. @@ -548,37 +436,24 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct |> Printf.sprintf "[%s]" ) string - ; field "upd_ids_of_failed_livepatches" - (fun (r : input_t) -> - r.upd_ids_of_failed_livepatches - |> String.concat ";" - |> Printf.sprintf "[%s]" - ) + ; field "kind" + (fun (r : input_t) -> Guidance.kind_to_string r.kind) string ] let string_of_input_t = Fmt.(str "%a" Dump.(record @@ fields_of_input)) - let string_of_output_t g = - Fmt.(str "%a" Dump.(string)) (UpdateInfo.guidance_to_string g) + let string_of_output_t l = + Fmt.(str "%a" Dump.(list string)) (List.map Guidance.to_string l) end - let transform - Io. - { - updates_info - ; update - ; upd_ids_of_livepatches - ; upd_ids_of_failed_livepatches - } = - eval_guidance_for_one_update ~updates_info ~update - ~kind:Guidance.Recommended + let transform Io.{updates_info; update; upd_ids_of_livepatches; kind} = + eval_guidance_for_one_update ~updates_info ~update ~kind ~upd_ids_of_livepatches:(UpdateIdSet.of_list upd_ids_of_livepatches) - ~upd_ids_of_failed_livepatches: - (UpdateIdSet.of_list upd_ids_of_failed_livepatches) let tests = let open Io in + let open Guidance in `QuickAndAutoDocumented [ (* Update ID in update can't be found in updateinfo list *) @@ -600,9 +475,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Mandatory } - , None + , [] ) ; (* Update ID in update can't be found in updateinfo list *) ( { @@ -614,16 +489,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.EvacuateHost - ; abs_guidance= Some Guidance.RebootHost + ; guidance= + [ + (Mandatory, [EvacuateHost]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -632,16 +512,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.EvacuateHost - ; abs_guidance= Some Guidance.RebootHost + ; guidance= + [ + (Mandatory, [EvacuateHost]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -661,9 +546,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Mandatory } - , None + , [] ) ; (* No update ID in update *) ( { @@ -675,16 +560,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.EvacuateHost - ; abs_guidance= Some Guidance.RebootHost + ; guidance= + [ + (Mandatory, [EvacuateHost]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -703,9 +593,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , None + , [] ) ; (* Empty applicabilities *) ( { @@ -717,16 +607,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -735,16 +630,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RebootHost]) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -763,9 +663,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , Some Guidance.RebootHost + , [RebootHost] ) ; (* Matched applicability *) ( { @@ -777,16 +677,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -795,8 +700,13 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RestartDeviceModel - ; abs_guidance= None + ; guidance= + [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [ Applicability. @@ -814,10 +724,10 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -836,9 +746,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Mandatory } - , Some Guidance.RestartDeviceModel + , [RestartDeviceModel] ) ; (* Matched in multiple applicabilities *) ( { @@ -850,16 +760,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -868,8 +783,13 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RestartDeviceModel - ; abs_guidance= None + ; guidance= + [ + (Mandatory, [EvacuateHost]) + ; (Recommended, [RestartDeviceModel]) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [ Applicability. @@ -898,10 +818,10 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -920,9 +840,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , Some Guidance.RestartDeviceModel + , [RestartDeviceModel] ) ; (* No matched applicability *) ( { @@ -934,16 +854,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -952,8 +877,13 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RestartDeviceModel - ; abs_guidance= None + ; guidance= + [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [ Applicability. @@ -971,10 +901,10 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -993,9 +923,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Mandatory } - , None + , [] ) ; (* Unmatched arch *) ( { @@ -1007,16 +937,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -1025,8 +960,13 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RestartDeviceModel - ; abs_guidance= None + ; guidance= + [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [ Applicability. @@ -1043,10 +983,10 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1065,9 +1005,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Mandatory } - , None + , [] ) ; (* Matched in multiple applicabilities with epoch *) ( { @@ -1079,16 +1019,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -1097,8 +1042,13 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RestartDeviceModel - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RestartDeviceModel; RestartToolstack]) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [ Applicability. @@ -1127,10 +1077,10 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1149,11 +1099,11 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= [] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , Some Guidance.RestartDeviceModel + , [RestartDeviceModel; RestartToolstack] ) - ; (* livepatch_guidance: Some _ *) + ; (* livepatch_guidance *) ( { updates_info= [ @@ -1163,13 +1113,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RebootHost]) + ; (Full, []) + ; (Livepatch, [RestartDeviceModel; RestartToolstack]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartDeviceModel ; livepatches= [ LivePatch. @@ -1195,6 +1149,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1213,11 +1168,11 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= ["UPDATE-0000"] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , Some Guidance.RestartDeviceModel + , [RestartDeviceModel; RestartToolstack] ) - ; (* livepatch_guidance - None *) + ; (* livepatch_guidance - empty *) ( { updates_info= [ @@ -1227,13 +1182,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RebootHost]) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [ LivePatch. @@ -1249,6 +1208,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1267,9 +1227,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= ["UPDATE-0000"] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , None + , [] ) ; (* livepatch_guidance: livepatch does not come from RPM update UPDATE-0001. * And the RPM update UPDATE-0001 requires RebootHost. @@ -1283,13 +1243,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, [RestartDeviceModel]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartDeviceModel ; livepatches= [ LivePatch. @@ -1305,6 +1269,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -1313,13 +1278,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RebootHost]) + ; (Full, []) + ; (Livepatch, [RestartToolstack]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartToolstack ; livepatches= [ LivePatch. @@ -1335,6 +1304,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1353,9 +1323,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= ["UPDATE-0000"] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , Some Guidance.RebootHost + , [RebootHost] ) ; (* livepatch_guidance: livepatch comes from the RPM update UPDATE-001 *) ( { @@ -1367,13 +1337,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RebootHost]) + ; (Full, []) + ; (Livepatch, [RestartDeviceModel]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartDeviceModel ; livepatches= [ LivePatch. @@ -1389,6 +1363,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -1397,13 +1372,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RebootHost]) + ; (Full, []) + ; (Livepatch, [RestartToolstack]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartToolstack ; livepatches= [ LivePatch. @@ -1429,6 +1408,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1447,11 +1427,11 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= ["UPDATE-0001"] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , Some Guidance.RestartToolstack + , [RestartToolstack] ) - ; (* livepatch_guidance: latest update doesn't have livepatch and recommendedGuidance is None *) + ; (* livepatch_guidance: latest update doesn't have livepatch and recommended is empty *) ( { updates_info= [ @@ -1461,13 +1441,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, [RestartDeviceModel]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [ LivePatch. @@ -1483,6 +1467,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -1491,16 +1476,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RestartToolstack]) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1519,11 +1509,11 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= ["UPDATE-0000"] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , None + , [RestartToolstack] ) - ; (* livepatch_guidance: latest update doesn't have livepatch and recommendedGuidance is RebootHost *) + ; (* livepatch_guidance: latest update doesn't have livepatch but recommended is RebootHost *) ( { updates_info= [ @@ -1533,13 +1523,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, [RestartToolstack]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [ LivePatch. @@ -1555,6 +1549,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -1563,16 +1558,21 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RebootHost]) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1591,11 +1591,11 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= ["UPDATE-0000"] - ; upd_ids_of_failed_livepatches= [] + ; kind= Recommended } - , Some Guidance.RebootHost + , [RebootHost] ) - ; (* livepatch_guidance: failure of applying livepatch *) + ; (* livepatch_guidance: is overwhelmed by aother update *) ( { updates_info= [ @@ -1605,13 +1605,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RestartVM]) + ; (Full, []) + ; (Livepatch, [RestartVM]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartToolstack ; livepatches= [ LivePatch. @@ -1637,6 +1641,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ; ( "UPDATE-0001" @@ -1645,13 +1650,17 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, [RebootHost]) + ; (Full, []) + ; (Livepatch, [RestartDeviceModel]) + ] ; guidance_applicabilities= [] (* No applicabilities *) ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [ LivePatch. @@ -1667,6 +1676,7 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } ) ] @@ -1685,9 +1695,9 @@ module EvalGuidanceForOneUpdate = Generic.MakeStateless (struct ; repository= "regular" } ; upd_ids_of_livepatches= ["UPDATE-0000"] - ; upd_ids_of_failed_livepatches= ["UPDATE-0001"] + ; kind= Recommended } - , None + , [RebootHost] ) ] end) @@ -2000,46 +2010,71 @@ module ConsolidateUpdatesOfHost = Generic.MakeStateless (struct id= "" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [(Mandatory, []); (Recommended, []); (Full, []); (Livepatch, [])] ; guidance_applicabilities= [] ; spec_info= "special info" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } let updates_info = + let open Guidance in [ ( "UPDATE-0000" , { updateinfo with id= "UPDATE-0000" - ; rec_guidance= Some Guidance.EvacuateHost + ; guidance= + [ + (Mandatory, [EvacuateHost]) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] } ) ; ( "UPDATE-0001" , { updateinfo with id= "UPDATE-0001" - ; rec_guidance= Some Guidance.RebootHost + ; guidance= + [ + (Mandatory, [RebootHost]) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] } ) ; ( "UPDATE-0002" , { updateinfo with id= "UPDATE-0002" - ; rec_guidance= Some Guidance.RestartDeviceModel + ; guidance= + [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] } ) ; ( "UPDATE-0003" , { updateinfo with id= "UPDATE-0003" - ; rec_guidance= Some Guidance.EvacuateHost + ; guidance= + [ + (Mandatory, [EvacuateHost]) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] } ) ] @@ -2064,8 +2099,14 @@ module ConsolidateUpdatesOfHost = Generic.MakeStateless (struct , ( `Assoc [ ("ref", `String host) - ; ("recommended-guidance", `List []) - ; ("absolute-guidance", `List []) + ; ( "guidance" + , `Assoc + [ + ("mandatory", `List []) + ; ("recommended", `List []) + ; ("full", `List []) + ] + ) ; ("RPMS", `List []) ; ("updates", `List []) ; ("livepatches", `List []) @@ -2144,8 +2185,14 @@ module ConsolidateUpdatesOfHost = Generic.MakeStateless (struct , ( `Assoc [ ("ref", `String host) - ; ("recommended-guidance", `List [`String "RebootHost"]) - ; ("absolute-guidance", `List []) + ; ( "guidance" + , `Assoc + [ + ("mandatory", `List [`String "RebootHost"]) + ; ("recommended", `List []) + ; ("full", `List []) + ] + ) ; ( "RPMS" , `List [ @@ -2201,8 +2248,14 @@ module ConsolidateUpdatesOfHost = Generic.MakeStateless (struct , ( `Assoc [ ("ref", `String host) - ; ("recommended-guidance", `List [`String "RebootHost"]) - ; ("absolute-guidance", `List []) + ; ( "guidance" + , `Assoc + [ + ("mandatory", `List [`String "RebootHost"]) + ; ("recommended", `List []) + ; ("full", `List []) + ] + ) ; ("RPMS", `List [`String "libpath-utils-0.2.2-9.el7.noarch.rpm"]) ; ("updates", `List [`String "UPDATE-0001"]) ; ("livepatches", `List []) @@ -2252,8 +2305,14 @@ module ConsolidateUpdatesOfHost = Generic.MakeStateless (struct , ( `Assoc [ ("ref", `String host) - ; ("recommended-guidance", `List [`String "EvacuateHost"]) - ; ("absolute-guidance", `List []) + ; ( "guidance" + , `Assoc + [ + ("mandatory", `List [`String "EvacuateHost"]) + ; ("recommended", `List []) + ; ("full", `List []) + ] + ) ; ("RPMS", `List [`String "libpath-utils-0.2.2-9.el7.noarch.rpm"]) ; ("updates", `List [`String "UPDATE-0003"]) ; ("livepatches", `List []) @@ -2330,8 +2389,14 @@ module ConsolidateUpdatesOfHost = Generic.MakeStateless (struct , ( `Assoc [ ("ref", `String host) - ; ("recommended-guidance", `List [`String "RebootHost"]) - ; ("absolute-guidance", `List []) + ; ( "guidance" + , `Assoc + [ + ("mandatory", `List [`String "RebootHost"]) + ; ("recommended", `List []) + ; ("full", `List []) + ] + ) ; ( "RPMS" , `List [ @@ -2414,8 +2479,14 @@ module ConsolidateUpdatesOfHost = Generic.MakeStateless (struct , ( `Assoc [ ("ref", `String host) - ; ("recommended-guidance", `List [`String "RebootHost"]) - ; ("absolute-guidance", `List []) + ; ( "guidance" + , `Assoc + [ + ("mandatory", `List [`String "RebootHost"]) + ; ("recommended", `List []) + ; ("full", `List []) + ] + ) ; ( "RPMS" , `List [ @@ -2530,8 +2601,14 @@ module ConsolidateUpdatesOfHost = Generic.MakeStateless (struct , ( `Assoc [ ("ref", `String host) - ; ("recommended-guidance", `List [`String "RebootHost"]) - ; ("absolute-guidance", `List []) + ; ( "guidance" + , `Assoc + [ + ("mandatory", `List [`String "RebootHost"]) + ; ("recommended", `List []) + ; ("full", `List []) + ] + ) ; ( "RPMS" , `List [ @@ -2750,52 +2827,725 @@ module ParseUpdateInfoList = Generic.MakeStateless (struct ] end) -module GuidanceSetResortGuidancesTest = Generic.MakeStateless (struct +module GuidanceSetResortTest = Generic.MakeStateless (struct module Io = struct - type input_t = Guidance.guidance_kind * Guidance.t list + type input_t = Guidance.t list type output_t = Guidance.t list - let string_of_input_t (kind, l) = - let kind' = - match kind with - | Guidance.Recommended -> - "Recommended" - | Guidance.Absolute -> - "Absolute" - in - kind' - ^ ", " - ^ Fmt.(str "%a" Dump.(list string)) (List.map Guidance.to_string l) - - let string_of_output_t l = + let string_of_input_t l = Fmt.(str "%a" Dump.(list string)) (List.map Guidance.to_string l) + + let string_of_output_t = string_of_input_t end - let transform (kind, guidances) = + let transform guidances = guidances |> GuidanceSet.of_list - |> GuidanceSet.resort_guidances - ~remove_evacuations:(kind = Guidance.Absolute) + |> GuidanceSet.resort |> GuidanceSet.elements let tests = + let open Guidance in + `QuickAndAutoDocumented + [ + ([], []) + ; ([EvacuateHost], [EvacuateHost]) + ; ([EvacuateHost; RestartDeviceModel], [EvacuateHost]) + ; ( [EvacuateHost; RestartDeviceModel; RestartToolstack] + , [RestartToolstack; EvacuateHost] + ) + ; ( [EvacuateHost; RestartDeviceModel; RestartToolstack; RebootHost] + , [RebootHost] + ) + ; ([EvacuateHost; RestartDeviceModel; RebootHost], [RebootHost]) + ; ([EvacuateHost; RestartToolstack], [RestartToolstack; EvacuateHost]) + ; ([EvacuateHost; RestartToolstack; RebootHost], [RebootHost]) + ; ([EvacuateHost; RebootHost], [RebootHost]) + ; ([RestartDeviceModel], [RestartDeviceModel]) + ; ( [RestartDeviceModel; RestartToolstack] + , [RestartToolstack; RestartDeviceModel] + ) + ; ([RestartDeviceModel; RestartToolstack; RebootHost], [RebootHost]) + ; ([RestartDeviceModel; RebootHost], [RebootHost]) + ; ([RestartToolstack], [RestartToolstack]) + ; ([RestartToolstack; RebootHost], [RebootHost]) + ; ([RebootHost], [RebootHost]) + ; ([RestartVM], [RestartVM]) + ; ([RestartVM; EvacuateHost], [EvacuateHost; RestartVM]) + ; ( [RestartVM; EvacuateHost; RestartDeviceModel] + , [EvacuateHost; RestartVM] + ) + ; ( [RestartVM; EvacuateHost; RestartDeviceModel; RestartToolstack] + , [RestartToolstack; EvacuateHost; RestartVM] + ) + ; ( [ + RestartVM + ; EvacuateHost + ; RestartDeviceModel + ; RestartToolstack + ; RebootHost + ] + , [RebootHost; RestartVM] + ) + ; ( [RestartVM; EvacuateHost; RestartDeviceModel; RebootHost] + , [RebootHost; RestartVM] + ) + ; ( [RestartVM; EvacuateHost; RestartToolstack] + , [RestartToolstack; EvacuateHost; RestartVM] + ) + ; ( [RestartVM; EvacuateHost; RestartToolstack; RebootHost] + , [RebootHost; RestartVM] + ) + ; ([RestartVM; EvacuateHost; RebootHost], [RebootHost; RestartVM]) + ; ([RestartVM; RestartDeviceModel], [RestartVM]) + ; ( [RestartVM; RestartDeviceModel; RestartToolstack] + , [RestartToolstack; RestartVM] + ) + ; ( [RestartVM; RestartDeviceModel; RestartToolstack; RebootHost] + , [RebootHost; RestartVM] + ) + ; ([RestartVM; RestartDeviceModel; RebootHost], [RebootHost; RestartVM]) + ; ([RestartVM; RestartToolstack], [RestartToolstack; RestartVM]) + ; ([RestartVM; RestartToolstack; RebootHost], [RebootHost; RestartVM]) + ; ([RestartVM; RebootHost], [RebootHost; RestartVM]) + ] +end) + +module GuidanceSetReduceTest = Generic.MakeStateless (struct + module Io = struct + type input_t = Guidance.t list * Guidance.t list + + type output_t = Guidance.t list + + let string_of_input_t (l1, l2) = + Fmt.( + str "%a + %a" + Dump.(list string) + (List.map Guidance.to_string l1) + Dump.(list string) + (List.map Guidance.to_string l2) + ) + + let string_of_output_t l = + Fmt.(str "%a" Dump.(list string)) (List.map Guidance.to_string l) + end + + let transform (l1, l2) = + let open GuidanceSet in + reduce (of_list l1) (of_list l2) |> elements + + let tests = + let open Guidance in `QuickAndAutoDocumented [ - ((Guidance.Recommended, [Guidance.RebootHost]), [Guidance.RebootHost]) - ; ( (Guidance.Recommended, [Guidance.RebootHost; Guidance.RebootHost]) - , [Guidance.RebootHost] + (([], []), []) + ; (([], [EvacuateHost]), [EvacuateHost]) + ; (([], [RebootHost]), [RebootHost]) + ; (([], [RestartDeviceModel]), [RestartDeviceModel]) + ; ( ([], [RestartToolstack; EvacuateHost]) + , [RestartToolstack; EvacuateHost] + ) + ; ( ([], [RestartToolstack; RestartDeviceModel]) + , [RestartToolstack; RestartDeviceModel] + ) + ; (([], [RestartToolstack]), [RestartToolstack]) + ; (([EvacuateHost], [EvacuateHost]), []) + ; (([EvacuateHost], [RebootHost]), [RebootHost]) + ; (([EvacuateHost], [RestartDeviceModel]), []) + ; (([EvacuateHost], [RestartToolstack; EvacuateHost]), [RestartToolstack]) + ; ( ([EvacuateHost], [RestartToolstack; RestartDeviceModel]) + , [RestartToolstack] + ) + ; (([EvacuateHost], [RestartToolstack]), [RestartToolstack]) + ; (([EvacuateHost], []), []) + ; (([RebootHost], [EvacuateHost]), []) + ; (([RebootHost], [RebootHost]), []) + ; (([RebootHost], [RestartDeviceModel]), []) + ; (([RebootHost], [RestartToolstack; EvacuateHost]), []) + ; (([RebootHost], [RestartToolstack; RestartDeviceModel]), []) + ; (([RebootHost], [RestartToolstack]), []) + ; (([RebootHost], []), []) + ; (([RestartDeviceModel], [EvacuateHost]), [EvacuateHost]) + ; (([RestartDeviceModel], [RebootHost]), [RebootHost]) + ; (([RestartDeviceModel], [RestartDeviceModel]), []) + ; ( ([RestartDeviceModel], [RestartToolstack; EvacuateHost]) + , [RestartToolstack; EvacuateHost] + ) + ; ( ([RestartDeviceModel], [RestartToolstack; RestartDeviceModel]) + , [RestartToolstack] + ) + ; (([RestartDeviceModel], [RestartToolstack]), [RestartToolstack]) + ; (([RestartDeviceModel], []), []) + ; (([RestartToolstack; EvacuateHost], [EvacuateHost]), []) + ; (([RestartToolstack; EvacuateHost], [RebootHost]), [RebootHost]) + ; (([RestartToolstack; EvacuateHost], [RestartDeviceModel]), []) + ; ( ([RestartToolstack; EvacuateHost], [RestartToolstack; EvacuateHost]) + , [] + ) + ; ( ( [RestartToolstack; EvacuateHost] + , [RestartToolstack; RestartDeviceModel] + ) + , [] + ) + ; (([RestartToolstack; EvacuateHost], [RestartToolstack]), []) + ; (([RestartToolstack; EvacuateHost], []), []) + ; ( ([RestartToolstack; RestartDeviceModel], [EvacuateHost]) + , [EvacuateHost] + ) + ; (([RestartToolstack; RestartDeviceModel], [RebootHost]), [RebootHost]) + ; (([RestartToolstack; RestartDeviceModel], [RestartDeviceModel]), []) + ; ( ( [RestartToolstack; RestartDeviceModel] + , [RestartToolstack; EvacuateHost] + ) + , [EvacuateHost] + ) + ; ( ( [RestartToolstack; RestartDeviceModel] + , [RestartToolstack; RestartDeviceModel] + ) + , [] + ) + ; (([RestartToolstack; RestartDeviceModel], [RestartToolstack]), []) + ; (([RestartToolstack; RestartDeviceModel], []), []) + ; (([RestartToolstack], [EvacuateHost]), [EvacuateHost]) + ; (([RestartToolstack], [RebootHost]), [RebootHost]) + ; (([RestartToolstack], [RestartDeviceModel]), [RestartDeviceModel]) + ; (([RestartToolstack], [RestartToolstack; EvacuateHost]), [EvacuateHost]) + ; ( ([RestartToolstack], [RestartToolstack; RestartDeviceModel]) + , [RestartDeviceModel] + ) + ; (([RestartToolstack], [RestartToolstack]), []) + ; (([RestartToolstack], []), []) + ; (([RestartVM; EvacuateHost], [EvacuateHost]), []) + ; (([RestartVM; EvacuateHost], [RebootHost; RestartVM]), [RebootHost]) + ; (([RestartVM; EvacuateHost], [RebootHost]), [RebootHost]) + ; (([RestartVM; EvacuateHost], [RestartDeviceModel]), []) + ; ( ([RestartVM; EvacuateHost], [RestartToolstack; EvacuateHost]) + , [RestartToolstack] + ) + ; ( ([RestartVM; EvacuateHost], [RestartToolstack; RestartDeviceModel]) + , [RestartToolstack] + ) + ; (([RestartVM; EvacuateHost], [RestartToolstack]), [RestartToolstack]) + ; (([RestartVM; EvacuateHost], [RestartVM; EvacuateHost]), []) + ; ( ( [RestartVM; EvacuateHost] + , [RestartVM; RestartToolstack; EvacuateHost] + ) + , [RestartToolstack] + ) + ; ( ([RestartVM; EvacuateHost], [RestartVM; RestartToolstack]) + , [RestartToolstack] + ) + ; (([RestartVM; EvacuateHost], [RestartVM]), []) + ; (([RestartVM; EvacuateHost], []), []) + ; (([RestartVM; RestartToolstack; EvacuateHost], [EvacuateHost]), []) + ; ( ([RestartVM; RestartToolstack; EvacuateHost], [RebootHost; RestartVM]) + , [RebootHost] + ) + ; ( ([RestartVM; RestartToolstack; EvacuateHost], [RebootHost]) + , [RebootHost] + ) + ; (([RestartVM; RestartToolstack; EvacuateHost], [RestartDeviceModel]), []) + ; ( ( [RestartVM; RestartToolstack; EvacuateHost] + , [RestartToolstack; EvacuateHost] + ) + , [] + ) + ; ( ( [RestartVM; RestartToolstack; EvacuateHost] + , [RestartToolstack; RestartDeviceModel] + ) + , [] + ) + ; (([RestartVM; RestartToolstack; EvacuateHost], [RestartToolstack]), []) + ; ( ( [RestartVM; RestartToolstack; EvacuateHost] + , [RestartVM; EvacuateHost] + ) + , [] ) - ; ( ( Guidance.Recommended - , [Guidance.RebootHost; Guidance.RestartDeviceModel] + ; ( ( [RestartVM; RestartToolstack; EvacuateHost] + , [RestartVM; RestartToolstack; EvacuateHost] ) - , [Guidance.RebootHost] + , [] ) - ; ((Guidance.Absolute, [Guidance.EvacuateHost]), []) - ; ( ( Guidance.Recommended - , [Guidance.EvacuateHost; Guidance.RestartDeviceModel] + ; ( ( [RestartVM; RestartToolstack; EvacuateHost] + , [RestartVM; RestartToolstack] ) - , [Guidance.EvacuateHost] + , [] + ) + ; (([RestartVM; RestartToolstack; EvacuateHost], [RestartVM]), []) + ; (([RestartVM; RestartToolstack; EvacuateHost], []), []) + ; (([RestartVM; RestartToolstack], [EvacuateHost]), [EvacuateHost]) + ; (([RestartVM; RestartToolstack], [RebootHost; RestartVM]), [RebootHost]) + ; (([RestartVM; RestartToolstack], [RebootHost]), [RebootHost]) + ; (([RestartVM; RestartToolstack], [RestartDeviceModel]), []) + ; ( ([RestartVM; RestartToolstack], [RestartToolstack; EvacuateHost]) + , [EvacuateHost] + ) + ; ( ([RestartVM; RestartToolstack], [RestartToolstack; RestartDeviceModel]) + , [] + ) + ; (([RestartVM; RestartToolstack], [RestartToolstack]), []) + ; ( ([RestartVM; RestartToolstack], [RestartVM; EvacuateHost]) + , [EvacuateHost] + ) + ; ( ( [RestartVM; RestartToolstack] + , [RestartVM; RestartToolstack; EvacuateHost] + ) + , [EvacuateHost] + ) + ; (([RestartVM; RestartToolstack], [RestartVM; RestartToolstack]), []) + ; (([RestartVM; RestartToolstack], [RestartVM]), []) + ; (([RestartVM; RestartToolstack], []), []) + ; (([RestartVM], [EvacuateHost]), [EvacuateHost]) + ; (([RestartVM], [RebootHost; RestartVM]), [RebootHost]) + ; (([RestartVM], [RebootHost]), [RebootHost]) + ; (([RestartVM], [RestartDeviceModel]), []) + ; ( ([RestartVM], [RestartToolstack; EvacuateHost]) + , [RestartToolstack; EvacuateHost] + ) + ; ( ([RestartVM], [RestartToolstack; RestartDeviceModel]) + , [RestartToolstack] + ) + ; (([RestartVM], [RestartToolstack]), [RestartToolstack]) + ; (([RestartVM], [RestartVM; EvacuateHost]), [EvacuateHost]) + ; ( ([RestartVM], [RestartVM; RestartToolstack; EvacuateHost]) + , [RestartToolstack; EvacuateHost] + ) + ; (([RestartVM], [RestartVM; RestartToolstack]), [RestartToolstack]) + ; (([RestartVM], [RestartVM]), []) + ; (([RestartVM], []), []) + ; (([], [RestartVM; EvacuateHost]), [EvacuateHost; RestartVM]) + ; ( ([], [RestartVM; RestartToolstack; EvacuateHost]) + , [RestartToolstack; EvacuateHost; RestartVM] + ) + ; (([], [RestartVM; RestartToolstack]), [RestartToolstack; RestartVM]) + ; (([], [RestartVM]), [RestartVM]) + ] +end) + +module GuidanceSetReduceCascadedListTest = Generic.MakeStateless (struct + module Io = struct + type input_t = (Guidance.kind * Guidance.t list) list + + type output_t = input_t + + let string_of_input_t l = + let string_of_kind_guidances (kind, gs) = + Fmt.( + str "%a: %a" + Dump.(string) + (Guidance.kind_to_string kind) + Dump.(list string) + (List.map Guidance.to_string gs) + ) + in + Fmt.(str "%a" Dump.(list string)) (List.map string_of_kind_guidances l) + + let string_of_output_t = string_of_input_t + end + + let transform l = + l + |> List.map (fun (k, l') -> (k, GuidanceSet.of_list l')) + |> GuidanceSet.reduce_cascaded_list + |> List.map (fun (k, s') -> (k, GuidanceSet.elements s')) + + let tests = + let open Guidance in + `QuickAndAutoDocumented + [ + ([], []) + ; ( [(Mandatory, []); (Recommended, []); (Full, [])] + , [(Mandatory, []); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, [RestartToolstack; RestartDeviceModel]) + ] + , [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, [RestartToolstack; RestartDeviceModel]) + ] + ) + ; ( [ + (Mandatory, []) + ; (Recommended, [RestartToolstack]) + ; (Full, [RestartDeviceModel]) + ] + , [ + (Mandatory, []) + ; (Recommended, [RestartToolstack]) + ; (Full, [RestartDeviceModel]) + ] + ) + ; ( [ + (Mandatory, []) + ; (Recommended, [RestartToolstack]) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, []) + ; (Recommended, [RestartToolstack]) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, []) + ; (Recommended, [RestartToolstack; RestartDeviceModel]) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, []) + ; (Recommended, [RestartToolstack; RestartDeviceModel]) + ; (Full, [RebootHost]) + ] + ) + ; ( [(Mandatory, [RestartToolstack]); (Recommended, []); (Full, [])] + , [(Mandatory, [RestartToolstack]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RestartToolstack]) + ; (Recommended, []) + ; (Full, [RestartToolstack]) + ] + , [(Mandatory, [RestartToolstack]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RestartToolstack]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartToolstack]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RestartToolstack; RestartDeviceModel]) + ; (Recommended, []) + ; (Full, []) + ] + , [ + (Mandatory, [RestartToolstack; RestartDeviceModel]) + ; (Recommended, []) + ; (Full, []) + ] + ) + ; ( [ + (Mandatory, [RestartToolstack; RestartDeviceModel]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartToolstack; RestartDeviceModel]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, []) + ; (Full, [RestartDeviceModel]) + ] + , [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, []) + ; (Full, []) + ] + ) + ; ( [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, [RestartDeviceModel]) + ; (Full, [RestartToolstack]) + ] + , [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, []) + ; (Full, []) + ] + ) + ; ( [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, [RestartToolstack]) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, [RestartToolstack]) + ; (Full, [RebootHost]) + ] + ) + ; ( [(Mandatory, [RebootHost]); (Recommended, []); (Full, [])] + , [(Mandatory, [RebootHost]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, []) + ; (Full, [RestartToolstack; RestartDeviceModel]) + ] + , [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, []) + ; (Full, [RestartToolstack]) + ] + ) + ; ( [ + (Mandatory, [RebootHost]) + ; (Recommended, []) + ; (Full, [RestartToolstack; RestartDeviceModel]) + ] + , [(Mandatory, [RebootHost]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RebootHost]) + ; (Recommended, [RestartDeviceModel]) + ; (Full, [RestartToolstack]) + ] + , [(Mandatory, [RebootHost]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [EvacuateHost]) + ; (Recommended, []) + ; (Full, [RestartDeviceModel]) + ] + , [(Mandatory, [EvacuateHost]); (Recommended, []); (Full, [])] + ) + ; ( [(Mandatory, [EvacuateHost]); (Recommended, []); (Full, [RebootHost])] + , [(Mandatory, [EvacuateHost]); (Recommended, []); (Full, [RebootHost])] + ) + ; ( [ + (Mandatory, [RebootHost; RestartVM]) + ; (Recommended, []) + ; (Full, [RestartVM]) + ] + , [(Mandatory, [RebootHost; RestartVM]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, []) + ; (Full, [RebootHost; RestartVM]) + ] + , [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, []) + ; (Full, [RebootHost; RestartVM]) + ] + ) + ; ( [ + (Mandatory, [RebootHost; RestartVM]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RebootHost; RestartVM]) + ] + , [(Mandatory, [RebootHost; RestartVM]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RebootHost]) + ; (Recommended, [RestartVM; EvacuateHost]) + ; (Full, [RebootHost]) + ] + , [(Mandatory, [RebootHost]); (Recommended, [RestartVM]); (Full, [])] + ) + ; ( [ + (Mandatory, [RebootHost]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RestartVM]) + ] + , [(Mandatory, [RebootHost]); (Recommended, []); (Full, [RestartVM])] + ) + ; ( [ + (Mandatory, [RebootHost; RestartVM]) + ; (Recommended, [RestartToolstack]) + ; (Full, [RebootHost; RestartVM]) + ] + , [(Mandatory, [RebootHost; RestartVM]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RestartToolstack]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RestartVM]) + ] + , [ + (Mandatory, [RestartToolstack]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RestartVM]) + ] + ) + ; ( [ + (Mandatory, [RestartVM; RestartToolstack]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartToolstack; RestartVM]) + ; (Recommended, []) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, [RestartVM]) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, [RestartVM]) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RebootHost; RestartVM]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RebootHost]) + ] + , [(Mandatory, [RebootHost; RestartVM]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RestartVM; RestartToolstack]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RebootHost; RestartVM]) + ] + , [ + (Mandatory, [RestartToolstack; RestartVM]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, []) + ; (Full, [RestartVM]) + ] + , [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, []) + ; (Full, [RestartVM]) + ] + ) + ; ( [ + (Mandatory, [RestartVM]) + ; (Recommended, [RestartToolstack]) + ; (Full, [RebootHost; RestartVM]) + ] + , [ + (Mandatory, [RestartVM]) + ; (Recommended, [RestartToolstack]) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RebootHost]) + ; (Recommended, []) + ; (Full, [RebootHost; RestartVM]) + ] + , [(Mandatory, [RebootHost]); (Recommended, []); (Full, [RestartVM])] + ) + ; ( [ + (Mandatory, [RebootHost]) + ; (Recommended, [RestartDeviceModel]) + ; (Full, [RestartVM]) + ] + , [(Mandatory, [RebootHost]); (Recommended, []); (Full, [RestartVM])] + ) + ; ( [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, [RestartToolstack]) + ; (Full, [RestartVM]) + ] + , [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, [RestartToolstack]) + ; (Full, [RestartVM]) + ] + ) + ; ( [ + (Mandatory, [RestartToolstack]) + ; (Recommended, [RestartVM; EvacuateHost]) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartToolstack]) + ; (Recommended, [EvacuateHost; RestartVM]) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RebootHost]) + ; (Recommended, [RestartToolstack; EvacuateHost]) + ; (Full, [RebootHost; RestartVM]) + ] + , [(Mandatory, [RebootHost]); (Recommended, []); (Full, [RestartVM])] + ) + ; ( [ + (Mandatory, [RestartVM]) + ; (Recommended, [RestartDeviceModel]) + ; (Full, [RestartVM]) + ] + , [(Mandatory, [RestartVM]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RestartVM; RestartToolstack]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartToolstack; RestartVM]) + ; (Recommended, [EvacuateHost]) + ; (Full, [RebootHost]) + ] + ) + ; ( [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, [RebootHost]) + ; (Full, [RestartVM]) + ] + , [ + (Mandatory, [RestartDeviceModel]) + ; (Recommended, [RebootHost]) + ; (Full, [RestartVM]) + ] + ) + ; ( [ + (Mandatory, [RebootHost; RestartVM]) + ; (Recommended, [RestartToolstack; RestartDeviceModel]) + ; (Full, [RebootHost; RestartVM]) + ] + , [(Mandatory, [RebootHost; RestartVM]); (Recommended, []); (Full, [])] + ) + ; ( [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, [RestartDeviceModel]) + ; (Full, [RestartVM]) + ] + , [ + (Mandatory, [RestartToolstack; EvacuateHost]) + ; (Recommended, []) + ; (Full, [RestartVM]) + ] + ) + ; ( [ + (Mandatory, [RestartToolstack]) + ; (Recommended, [RestartVM]) + ; (Full, [RebootHost]) + ] + , [ + (Mandatory, [RestartToolstack]) + ; (Recommended, [RestartVM]) + ; (Full, [RebootHost]) + ] ) ] end) @@ -3141,16 +3891,16 @@ module PruneUpdateInfoForLivepatches = Generic.MakeStateless (struct id= "UPDATE-00" ; summary= "SUMMARY" ; description= "DESCRIPTION" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [(Mandatory, []); (Recommended, []); (Full, []); (Livepatch, [])] ; guidance_applicabilities= [] ; spec_info= "SPEC_INFO" ; url= "URL" ; update_type= "UPDATE_TYPE" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } let tests = @@ -3626,18 +4376,392 @@ module GetLatestUpdatesFromRedundancy = Generic.MakeStateless (struct ] end) +module SetPendingGuidance = Generic.MakeStateless (struct + module Io = struct + (* ([host pending guidance list], [VMs pending guidance lists]), [coming guidance list] *) + type input_t = + (Guidance.t list * (string * Guidance.t list) list) * Guidance.t list + + (* ([host pending guidance list], [VMs pending guidance lists]) *) + type output_t = Guidance.t list * (string * Guidance.t list) list + + let string_of_pending (host_pending, vms_pending) = + Fmt.( + str "Host: %a; VMs: %a" + Dump.(list string) + (List.map Guidance.to_string host_pending) + Dump.(list (pair string (list string))) + (List.map + (fun (vm, l) -> (vm, List.map Guidance.to_string l)) + vms_pending + ) + ) + + let string_of_input_t (pending, coming) = + Fmt.( + str "Pending: %s. Coming: %a" + (string_of_pending pending) + Dump.(list string) + (List.map Guidance.to_string coming) + ) + + let string_of_output_t = string_of_pending + end + + let transform ((host_pending, vms_pending), coming) = + (* Use two hash tables to simulate the host's pending list and the VMs' pending lists *) + let host_tbl : + ( string + , [ `reboot_host + | `restart_toolstack + | `reboot_host_on_livepatch_failure + | `reboot_host_on_xen_livepatch_failure + | `reboot_host_on_kernel_livepatch_failure ] + list + ) + Hashtbl.t = + Hashtbl.create 1 + in + let vms_tbl : (string, [`restart_device_model | `restart_vm] list) Hashtbl.t + = + Hashtbl.create 3 + in + (* Only one host in the table *) + let host_ref = "host_ref" in + let open Guidance in + let host_to_pending = function + | RebootHost -> + Some `reboot_host + | RebootHostOnLivePatchFailure -> + Some `reboot_host_on_livepatch_failure + | RebootHostOnXenLivePatchFailure -> + Some `reboot_host_on_xen_livepatch_failure + | RebootHostOnKernelLivePatchFailure -> + Some `reboot_host_on_kernel_livepatch_failure + | RestartToolstack -> + Some `restart_toolstack + | _ -> + None + in + let vm_to_pending = function + | RestartDeviceModel -> + Some `restart_device_model + | RestartVM -> + Some `restart_vm + | _ -> + None + in + let to_guidance = Guidance.of_pending_guidance in + Hashtbl.add host_tbl host_ref (List.filter_map host_to_pending host_pending) ; + let vm_mapper (vm_ref, l) = (vm_ref, List.filter_map vm_to_pending l) in + Hashtbl.add_seq vms_tbl (List.to_seq (List.map vm_mapper vms_pending)) ; + let ops = + let host_get () = + Hashtbl.find host_tbl host_ref |> List.map to_guidance + in + let host_add value = + Hashtbl.find host_tbl host_ref + |> List.cons value + |> Hashtbl.replace host_tbl host_ref + in + let host_remove value = + Hashtbl.find host_tbl host_ref + |> List.filter (fun g -> g <> value) + |> Hashtbl.replace host_tbl host_ref + in + let vms_get () = + Hashtbl.to_seq vms_tbl + |> List.of_seq + |> List.map (fun (vm_ref, l) -> (vm_ref, List.map to_guidance l)) + in + let vm_add vm_ref value = + Hashtbl.find vms_tbl vm_ref + |> List.cons value + |> Hashtbl.replace vms_tbl vm_ref + in + let vm_remove vm_ref value = + Hashtbl.find vms_tbl vm_ref + |> List.filter (fun g -> g <> value) + |> Hashtbl.replace vms_tbl vm_ref + in + {host_get; host_add; host_remove; vms_get; vm_add; vm_remove} + in + (* transform *) + set_pending_guidances ~ops ~coming ; + (* return result *) + ( ops.host_get () + |> List.sort (fun g1 g2 -> String.compare (to_string g1) (to_string g2)) + , ops.vms_get () |> List.sort (fun (k1, _) (k2, _) -> String.compare k1 k2) + ) + + let tests = + let open Guidance in + `QuickAndAutoDocumented + [ + ((([], []), []), ([], [])) + ; ((([], []), [RebootHost]), ([RebootHost], [])) + ; ( (([], [("vm1", [RestartDeviceModel]); ("vm2", [])]), [RebootHost]) + , ([RebootHost], [("vm1", [RestartDeviceModel]); ("vm2", [])]) + ) + ; ((([], []), [RestartDeviceModel]), ([], [])) + ; ( (([RebootHost], [("vm1", []); ("vm2", [])]), [RebootHost]) + , ([RebootHost], [("vm1", []); ("vm2", [])]) + ) + ; ( (([RestartToolstack], [("vm1", []); ("vm2", [])]), [RebootHost]) + , ([RebootHost; RestartToolstack], [("vm1", []); ("vm2", [])]) + ) + ; ( ( ([RestartToolstack], [("vm1", [RestartDeviceModel]); ("vm2", [])]) + , [RebootHost] + ) + , ( [RebootHost; RestartToolstack] + , [("vm1", [RestartDeviceModel]); ("vm2", [])] + ) + ) + ; ( ( ([RestartToolstack], [("vm1", [RestartDeviceModel]); ("vm2", [])]) + , [RestartDeviceModel] + ) + , ( [RestartToolstack] + , [("vm1", [RestartDeviceModel]); ("vm2", [RestartDeviceModel])] + ) + ) + ; ( (([RebootHostOnLivePatchFailure], [("vm1", [])]), [RebootHost]) + , ([RebootHost; RebootHostOnLivePatchFailure], [("vm1", [])]) + ) + ; ( (([RebootHost], [("vm1", [])]), [RebootHostOnLivePatchFailure]) + , ([RebootHost; RebootHostOnLivePatchFailure], [("vm1", [])]) + ) + ; ( ( ( [] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + , [RebootHost] + ) + , ( [RebootHost] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + ) + ; ( ( ( [RestartToolstack] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + , [RebootHost] + ) + , ( [RebootHost; RestartToolstack] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + ) + ; ( ( ( [RestartToolstack] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + , [RestartToolstack] + ) + , ( [RestartToolstack] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + ) + ; ( ( ( [RestartToolstack] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + , [RestartDeviceModel] + ) + , ( [RestartToolstack] + , [ + ("vm1", [RestartDeviceModel]) + ; ("vm2", [RestartDeviceModel]) + ; ("vm3", [RestartDeviceModel; RestartVM]) + ] + ) + ) + ; ( ( ( [RestartToolstack] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + , [RestartVM] + ) + , ( [RestartToolstack] + , [ + ("vm1", [RestartVM]) + ; ("vm2", [RestartVM; RestartDeviceModel]) + ; ("vm3", [RestartVM]) + ] + ) + ) + ; ( ( ( [RestartToolstack; RebootHostOnXenLivePatchFailure] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + , [RestartToolstack] + ) + , ( [RebootHostOnXenLivePatchFailure; RestartToolstack] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + ) + ; ( ( ( [RebootHost; RebootHostOnKernelLivePatchFailure] + , [("vm1", []); ("vm2", [RestartDeviceModel]); ("vm3", [RestartVM])] + ) + , [RestartToolstack; RestartVM] + ) + , ( [RebootHost; RebootHostOnKernelLivePatchFailure; RestartToolstack] + , [ + ("vm1", [RestartVM]) + ; ("vm2", [RestartVM; RestartDeviceModel]) + ; ("vm3", [RestartVM]) + ] + ) + ) + ] +end) + +module MergeLivepatchFailures = Generic.MakeStateless (struct + module Io = struct + (* (previous_failures, (applied, failed)) *) + type input_t = + Guidance.t list * (Livepatch.component list * Livepatch.component list) + + (* to_be_removed, to_be_added *) + type output_t = Guidance.t list * Guidance.t list + + let string_of_input_t (previous_failures, (applied, failed)) = + Fmt.(str "%a" Dump.(pair (list string) (pair (list string) (list string)))) + ( List.map Guidance.to_string previous_failures + , ( List.map Livepatch.string_of_component applied + , List.map Livepatch.string_of_component failed + ) + ) + + let string_of_output_t (to_be_removed, to_be_added) = + Fmt.(str "%a" Dump.(pair (list string) (list string))) + ( List.map Guidance.to_string to_be_removed + , List.map Guidance.to_string to_be_added + ) + end + + let transform (previous_failures, (applied, failed)) = + merge_livepatch_failures ~previous_failures ~applied ~failed + + let tests = + let open Guidance in + let open Livepatch in + `QuickAndAutoDocumented + [ + (([], ([Xen; Kernel], [])), ([], [])) + ; (([], ([Kernel], [Xen])), ([], [RebootHostOnXenLivePatchFailure])) + ; (([], ([Kernel], [])), ([], [])) + ; (([], ([Xen], [Kernel])), ([], [RebootHostOnKernelLivePatchFailure])) + ; (([], ([Xen], [])), ([], [])) + ; ( ([], ([], [Xen; Kernel])) + , ( [] + , [ + RebootHostOnKernelLivePatchFailure; RebootHostOnXenLivePatchFailure + ] + ) + ) + ; (([], ([], [Kernel])), ([], [RebootHostOnKernelLivePatchFailure])) + ; (([], ([], [Xen])), ([], [RebootHostOnXenLivePatchFailure])) + ; (([], ([], [])), ([], [])) + ; ( ([RebootHostOnXenLivePatchFailure], ([Xen; Kernel], [])) + , ([RebootHostOnXenLivePatchFailure], []) + ) + ; (([RebootHostOnXenLivePatchFailure], ([Kernel], [Xen])), ([], [])) + ; (([RebootHostOnXenLivePatchFailure], ([Kernel], [])), ([], [])) + ; ( ([RebootHostOnXenLivePatchFailure], ([Xen], [Kernel])) + , ( [RebootHostOnXenLivePatchFailure] + , [RebootHostOnKernelLivePatchFailure] + ) + ) + ; ( ([RebootHostOnXenLivePatchFailure], ([Xen], [])) + , ([RebootHostOnXenLivePatchFailure], []) + ) + ; ( ([RebootHostOnXenLivePatchFailure], ([], [Xen; Kernel])) + , ([], [RebootHostOnKernelLivePatchFailure]) + ) + ; ( ([RebootHostOnXenLivePatchFailure], ([], [Kernel])) + , ([], [RebootHostOnKernelLivePatchFailure]) + ) + ; (([RebootHostOnXenLivePatchFailure], ([], [Xen])), ([], [])) + ; (([RebootHostOnXenLivePatchFailure], ([], [])), ([], [])) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([Xen; Kernel], []) + ) + , ( [RebootHostOnKernelLivePatchFailure; RebootHostOnXenLivePatchFailure] + , [] + ) + ) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([Kernel], [Xen]) + ) + , ([RebootHostOnKernelLivePatchFailure], []) + ) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([Kernel], []) + ) + , ([RebootHostOnKernelLivePatchFailure], []) + ) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([Xen], [Kernel]) + ) + , ([RebootHostOnXenLivePatchFailure], []) + ) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([Xen], []) + ) + , ([RebootHostOnXenLivePatchFailure], []) + ) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([], [Xen; Kernel]) + ) + , ([], []) + ) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([], [Kernel]) + ) + , ([], []) + ) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([], [Xen]) + ) + , ([], []) + ) + ; ( ( [RebootHostOnXenLivePatchFailure; RebootHostOnKernelLivePatchFailure] + , ([], []) + ) + , ([], []) + ) + ; ( ([RebootHostOnKernelLivePatchFailure], ([Xen; Kernel], [])) + , ([RebootHostOnKernelLivePatchFailure], []) + ) + ; ( ([RebootHostOnKernelLivePatchFailure], ([Kernel], [Xen])) + , ( [RebootHostOnKernelLivePatchFailure] + , [RebootHostOnXenLivePatchFailure] + ) + ) + ; ( ([RebootHostOnKernelLivePatchFailure], ([Kernel], [])) + , ([RebootHostOnKernelLivePatchFailure], []) + ) + ; (([RebootHostOnKernelLivePatchFailure], ([Xen], [Kernel])), ([], [])) + ; (([RebootHostOnKernelLivePatchFailure], ([Xen], [])), ([], [])) + ; ( ([RebootHostOnKernelLivePatchFailure], ([], [Xen; Kernel])) + , ([], [RebootHostOnXenLivePatchFailure]) + ) + ; (([RebootHostOnKernelLivePatchFailure], ([], [Kernel])), ([], [])) + ; ( ([RebootHostOnKernelLivePatchFailure], ([], [Xen])) + , ([], [RebootHostOnXenLivePatchFailure]) + ) + ; (([RebootHostOnKernelLivePatchFailure], ([], [])), ([], [])) + ] +end) + let tests = make_suite "repository_helpers_" [ ("update_of_json", UpdateOfJsonTest.tests) - ; ("assert_valid_guidances", GuidanceSetAssertValidGuidanceTest.tests) ; ("assert_url_is_valid", AssertUrlIsValid.tests) ; ("write_yum_config", WriteYumConfig.tests) ; ("eval_guidance_for_one_update", EvalGuidanceForOneUpdate.tests) ; ("get_update_in_json", GetUpdateInJson.tests) ; ("consolidate_updates_of_host", ConsolidateUpdatesOfHost.tests) ; ("parse_updateinfo_list", ParseUpdateInfoList.tests) - ; ("resort_guidances", GuidanceSetResortGuidancesTest.tests) + ; ("guidance_set_resort", GuidanceSetResortTest.tests) + ; ("guidance_set_reduce", GuidanceSetReduceTest.tests) + ; ( "guidance_set_reduce_cascaded_list" + , GuidanceSetReduceCascadedListTest.tests + ) ; ("prune_accumulative_updates", PruneAccumulativeUpdates.tests) ; ("prune_updateinfo_for_livepatches", PruneUpdateInfoForLivepatches.tests) ; ( "parse_output_of_yum_upgrade_dry_run" @@ -3646,6 +4770,8 @@ let tests = ; ( "get_latest_updates_from_redundancy" , GetLatestUpdatesFromRedundancy.tests ) + ; ("set_pending_guidances", SetPendingGuidance.tests) + ; ("merge_livepatch_failures", MergeLivepatchFailures.tests) ] let () = Alcotest.run "Repository Helpers" tests diff --git a/ocaml/tests/test_updateinfo.ml b/ocaml/tests/test_updateinfo.ml index b0a6142f1ca..def4fe0f65e 100644 --- a/ocaml/tests/test_updateinfo.ml +++ b/ocaml/tests/test_updateinfo.ml @@ -422,11 +422,8 @@ let fields_of_updateinfo = field "id" (fun (r : UpdateInfo.t) -> r.id) string ; field "summary" (fun (r : UpdateInfo.t) -> r.summary) string ; field "description" (fun (r : UpdateInfo.t) -> r.description) string - ; field "rec_guidance" - (fun (r : UpdateInfo.t) -> UpdateInfo.guidance_to_string r.rec_guidance) - string - ; field "abs_guidance" - (fun (r : UpdateInfo.t) -> UpdateInfo.guidance_to_string r.abs_guidance) + ; field "guidance" + (fun (r : UpdateInfo.t) -> GuidanceInUpdateInfo.to_string r.guidance) string ; field "guidance_applicabilities" (fun (r : UpdateInfo.t) -> @@ -436,11 +433,6 @@ let fields_of_updateinfo = ; field "spec_info" (fun (r : UpdateInfo.t) -> r.spec_info) string ; field "url" (fun (r : UpdateInfo.t) -> r.url) string ; field "update_type" (fun (r : UpdateInfo.t) -> r.update_type) string - ; field "livepatch_guidance" - (fun (r : UpdateInfo.t) -> - UpdateInfo.guidance_to_string r.livepatch_guidance - ) - string ; field "livepatches" (fun (r : UpdateInfo.t) -> List.map @@ -454,6 +446,7 @@ let fields_of_updateinfo = ; field "severity" (fun (r : UpdateInfo.t) -> Severity.to_string r.severity) string + ; field "title" (fun (r : UpdateInfo.t) -> r.title) string ] module UpdateInfoOfXml = Generic.MakeStateless (struct @@ -479,6 +472,7 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct try Ok (UpdateInfo.of_xml (Xml.parse_string input)) with e -> Error e let tests = + let open Guidance in `QuickAndAutoDocumented [ (* No "updates" node *) @@ -561,16 +555,21 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "title" } ) ] @@ -624,17 +623,22 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.of_string "2023-05-12T08:37:49Z" ; severity= Severity.High + ; title= "title" } ) ] @@ -674,17 +678,22 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.of_string "2023-05-12T08:37:49Z" ; severity= Severity.High + ; title= "title" } ) ; ( "UPDATE-0001" @@ -693,22 +702,27 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0001" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.of_string "2023-05-12T08:37:50Z" ; severity= Severity.None + ; title= "title" } ) ] ) - ; (* Single update with guidances *) + ; (* Single update with deprecated guidances only *) ( {| @@ -751,8 +765,13 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RestartDeviceModel - ; abs_guidance= Some Guidance.RebootHost + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] ; guidance_applicabilities= [ Applicability. @@ -777,16 +796,16 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.of_string "2023-05-12T08:37:49Z" ; severity= Severity.High + ; title= "title" } ) ] ) - ; (* Single update with new guidances *) + ; (* Single update with unknown guidance *) ( {| @@ -798,6 +817,18 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct https://update.details.info NewGuidance NewGuidance + + + NewGuidance + + + NewGuidance + RestartVM + + + NewGuidance + + xsconsole @@ -829,8 +860,13 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= Some Guidance.RebootHost - ; abs_guidance= Some Guidance.RebootHost + ; guidance= + [ + (Recommended, [RebootHost]) + ; (Full, [RebootHost; RestartVM]) + ; (Mandatory, [RebootHost]) + ; (Livepatch, []) + ] ; guidance_applicabilities= [ Applicability. @@ -855,16 +891,16 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.of_string "2023-05-12T08:37:49Z" ; severity= Severity.High + ; title= "title" } ) ] ) - ; (* Single update with livepatches and livepatch_guidance *) + ; (* Single update with livepatches and livepatch guidance *) ( {| @@ -874,6 +910,11 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct description special information https://update.details.info + + + RestartToolstack + + RestartToolstack @@ -893,13 +934,17 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Livepatch, [RestartToolstack]) + ; (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartToolstack ; livepatches= [ LivePatch. @@ -926,11 +971,12 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct ; issued= Xapi_stdext_date.Date.of_string "2023-05-12T08:37:49Z" ; severity= Severity.High + ; title= "title" } ) ] ) - ; (* Single update with livepatches and new livepatch_guidance *) + ; (* Single update with livepatches and unknown livepatch guidance *) ( {| @@ -941,6 +987,11 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct special information https://update.details.info + + + NewGuidance + + NewGuidance @@ -959,13 +1010,17 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Livepatch, [RebootHost]) + ; (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RebootHost ; livepatches= [ LivePatch. @@ -992,11 +1047,12 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct ; issued= Xapi_stdext_date.Date.of_string "2023-05-12T08:37:49Z" ; severity= Severity.High + ; title= "title" } ) ] ) - ; (* Single update with livepatch_guidance but empty livepatches *) + ; (* Single update with livepatch guidance but empty livepatch *) ( {| @@ -1005,6 +1061,11 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct summary description special information + + + RestartDeviceModel + + https://update.details.info RestartDeviceModel @@ -1021,21 +1082,26 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Livepatch, [RestartDeviceModel]) + ; (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartDeviceModel ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "title" } ) ] ) - ; (* Single update with invalid livepatches *) + ; (* Single update with valid livepatches *) ( {| @@ -1043,6 +1109,11 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct title summary description + + + RestartToolstack + + special information https://update.details.info @@ -1062,13 +1133,17 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Livepatch, [RestartToolstack]) + ; (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartToolstack ; livepatches= [ LivePatch. @@ -1084,6 +1159,7 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct ] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "title" } ) ] @@ -1096,6 +1172,11 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct title summary description + + + RestartToolstack + + special information https://update.details.info @@ -1115,16 +1196,350 @@ module UpdateInfoOfXml = Generic.MakeStateless (struct id= "UPDATE-0000" ; summary= "summary" ; description= "description" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= + [ + (Livepatch, [RestartToolstack]) + ; (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ] + ; guidance_applicabilities= [] + ; spec_info= "special information" + ; url= "https://update.details.info" + ; update_type= "security" + ; livepatches= [] + ; issued= Xapi_stdext_date.Date.epoch + ; severity= Severity.None + ; title= "title" + } + ) + ] + ) + ; (* guidance in new format: empty guidance *) + ( {| + + + UPDATE-0000 + title + summary + empty guidance + special information + https://update.details.info + EvacuateHost + RebootHost + + + RestartDeviceModel + + + + + + + |} + , Ok + [ + ( "UPDATE-0000" + , UpdateInfo. + { + id= "UPDATE-0000" + ; summary= "summary" + ; description= "empty guidance" + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] + ; guidance_applicabilities= [] + ; spec_info= "special information" + ; url= "https://update.details.info" + ; update_type= "security" + ; livepatches= [] + ; issued= Xapi_stdext_date.Date.epoch + ; severity= Severity.None + ; title= "title" + } + ) + ] + ) + ; (* guidance in new format only: empty guidance *) + ( {| + + + UPDATE-0000 + title + summary + guidance in new format only: empty guidance + special information + https://update.details.info + + + + + + + + + |} + , Ok + [ + ( "UPDATE-0000" + , UpdateInfo. + { + id= "UPDATE-0000" + ; summary= "summary" + ; description= "guidance in new format only: empty guidance" + ; guidance= + [ + (Mandatory, []) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] + ; guidance_applicabilities= [] + ; spec_info= "special information" + ; url= "https://update.details.info" + ; update_type= "security" + ; livepatches= [] + ; issued= Xapi_stdext_date.Date.epoch + ; severity= Severity.None + ; title= "title" + } + ) + ] + ) + ; (* guidance in new format: empty mandatory and full *) + ( {| + + + UPDATE-0000 + title + summary + empty mandatory and full + special information + https://update.details.info + EvacuateHost + RebootHost + + + + + + + RestartDeviceModel + + + + + + + |} + , Ok + [ + ( "UPDATE-0000" + , UpdateInfo. + { + id= "UPDATE-0000" + ; summary= "summary" + ; description= "empty mandatory and full" + ; guidance= + [ + (Full, []) + ; (Mandatory, []) + ; (Recommended, []) + ; (Livepatch, []) + ] + ; guidance_applicabilities= [] + ; spec_info= "special information" + ; url= "https://update.details.info" + ; update_type= "security" + ; livepatches= [] + ; issued= Xapi_stdext_date.Date.epoch + ; severity= Severity.None + ; title= "title" + } + ) + ] + ) + ; (* guidance in new format: mandatory only *) + ( {| + + + UPDATE-0000 + title + summary + mandatory only + special information + https://update.details.info + EvacuateHost + RebootHost + + + + RestartDeviceModel + EvacuateHost + RestartToolstack + + + RestartDeviceModel + + + + + + + |} + , Ok + [ + ( "UPDATE-0000" + , UpdateInfo. + { + id= "UPDATE-0000" + ; summary= "summary" + ; description= "mandatory only" + ; guidance= + [ + ( Mandatory + , [RestartDeviceModel; EvacuateHost; RestartToolstack] + ) + ; (Recommended, []) + ; (Full, []) + ; (Livepatch, []) + ] + ; guidance_applicabilities= [] + ; spec_info= "special information" + ; url= "https://update.details.info" + ; update_type= "security" + ; livepatches= [] + ; issued= Xapi_stdext_date.Date.epoch + ; severity= Severity.None + ; title= "title" + } + ) + ] + ) + ; (* guidance in new format: mandatory, recommended, full and livepatch *) + ( {| + + + UPDATE-0000 + title + summary + mandatory, recommended, full and livepatch + special information + https://update.details.info + EvacuateHost + RebootHost + + + + RestartToolstack + + + EvacuateHost + + + RestartDeviceModel + + + RebootHost + + + RestartDeviceModel + + + + + + + |} + , Ok + [ + ( "UPDATE-0000" + , UpdateInfo. + { + id= "UPDATE-0000" + ; summary= "summary" + ; description= "mandatory, recommended, full and livepatch" + ; guidance= + [ + (Full, [RebootHost]) + ; (Livepatch, [RestartDeviceModel]) + ; (Recommended, [EvacuateHost]) + ; (Mandatory, [RestartToolstack]) + ] + ; guidance_applicabilities= [] + ; spec_info= "special information" + ; url= "https://update.details.info" + ; update_type= "security" + ; livepatches= [] + ; issued= Xapi_stdext_date.Date.epoch + ; severity= Severity.None + ; title= "title" + } + ) + ] + ) + ; (* guidance in new format: mandatory, recommended, full and livepatch *) + ( {| + + + UPDATE-0000 + title + summary + RestartVM in mandatory + special information + https://update.details.info + EvacuateHost + RebootHost + + + + RestartVM + + + EvacuateHost + + + RestartDeviceModel + + + RebootHost + + + RestartDeviceModel + + + + + + + |} + , Ok + [ + ( "UPDATE-0000" + , UpdateInfo. + { + id= "UPDATE-0000" + ; summary= "summary" + ; description= "RestartVM in mandatory" + ; guidance= + [ + (Full, [RebootHost]) + ; (Livepatch, [RestartDeviceModel]) + ; (Recommended, [EvacuateHost]) + ; (Mandatory, [RestartVM]) + ] ; guidance_applicabilities= [] ; spec_info= "special information" ; url= "https://update.details.info" ; update_type= "security" - ; livepatch_guidance= Some Guidance.RestartToolstack ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "title" } ) ] diff --git a/ocaml/xapi-cli-protocol/cli_protocol.ml b/ocaml/xapi-cli-protocol/cli_protocol.ml index 6f3be830fa2..261bc11b187 100644 --- a/ocaml/xapi-cli-protocol/cli_protocol.ml +++ b/ocaml/xapi-cli-protocol/cli_protocol.ml @@ -34,6 +34,8 @@ type command = | Debug of string (* debug message to optionally display *) | Load of string (* filename *) | HttpGet of string * string (* filename * path *) + | PrintHttpGetJson of string (* path *) + | PrintUpdateGuidance of string (* path *) | HttpPut of string * string (* filename * path *) | HttpConnect of string (* path *) | Prompt (* request the user enter some text *) @@ -66,6 +68,10 @@ let string_of_command = function "Load " ^ x | HttpGet (filename, path) -> "HttpGet " ^ path ^ " -> " ^ filename + | PrintHttpGetJson path -> + "PrintHttpGetJson " ^ path ^ " -> stdout" + | PrintUpdateGuidance path -> + "PrintUpdateGuidance " ^ path ^ " -> stdout" | HttpPut (filename, path) -> "HttpPut " ^ path ^ " -> " ^ filename | HttpConnect path -> @@ -155,7 +161,7 @@ let unmarshal_list pos f = (*****************************************************************************) (* Marshal/Unmarshal higher-level messages *) -(* Highest command id: 17 *) +(* Highest command id: 19 *) let marshal_command = function | Print x -> @@ -166,6 +172,10 @@ let marshal_command = function marshal_int 1 ^ marshal_string x | HttpGet (a, b) -> marshal_int 12 ^ marshal_string a ^ marshal_string b + | PrintHttpGetJson a -> + marshal_int 18 ^ marshal_string a + | PrintUpdateGuidance a -> + marshal_int 19 ^ marshal_string a | HttpPut (a, b) -> marshal_int 13 ^ marshal_string a ^ marshal_string b | HttpConnect a -> @@ -216,6 +226,12 @@ let unmarshal_command pos = | 16 -> let body, pos = unmarshal_string pos in (PrintStderr body, pos) + | 18 -> + let a, pos = unmarshal_string pos in + (PrintHttpGetJson a, pos) + | 19 -> + let a, pos = unmarshal_string pos in + (PrintUpdateGuidance a, pos) | n -> raise (Unknown_tag ("command", n)) diff --git a/ocaml/xapi-cli-server/cli_frontend.ml b/ocaml/xapi-cli-server/cli_frontend.ml index 1803a8af640..55d884db9d8 100644 --- a/ocaml/xapi-cli-server/cli_frontend.ml +++ b/ocaml/xapi-cli-server/cli_frontend.ml @@ -1032,7 +1032,27 @@ let rec cmdtable_data : (string * cmd_spec) list = reqd= ["hash"] ; optn= [] ; help= "Apply updates from enabled repository on specified host." - ; implementation= No_fd Cli_operations.host_apply_updates + ; implementation= With_fd Cli_operations.host_apply_updates + ; flags= [Host_selectors] + } + ) + ; ( "host-emergency-clear-mandatory-guidance" + , { + reqd= [] + ; optn= [] + ; help= "Clear the pending mandatory guidance on this host" + ; implementation= + No_fd_local_session + Cli_operations.host_emergency_clear_mandatory_guidance + ; flags= [Neverforward] + } + ) + ; ( "host-updates-show-available" + , { + reqd= [] + ; optn= [] + ; help= "Show available updates for a specified host." + ; implementation= With_fd Cli_operations.host_updates_show_available ; flags= [Host_selectors] } ) diff --git a/ocaml/xapi-cli-server/cli_operations.ml b/ocaml/xapi-cli-server/cli_operations.ml index 95cdb32c9da..6766a5161f0 100644 --- a/ocaml/xapi-cli-server/cli_operations.ml +++ b/ocaml/xapi-cli-server/cli_operations.ml @@ -5462,6 +5462,37 @@ let wait_for_task_complete rpc session_id task_id = Thread.delay 1.0 done +let check_task_status ?(quiet_on_success = false) ~rpc ~session_id ~task ~fd + ~label ~ok () = + (* if the client thinks it's ok, check that the server does too *) + match Client.Task.get_status ~rpc ~session_id ~self:task with + | `success when ok && not quiet_on_success -> + marshal fd (Command (Print (Printf.sprintf "%s succeeded" label))) + | `success when ok && quiet_on_success -> + () + | `success -> + marshal fd + (Command + (PrintStderr (Printf.sprintf "%s failed, unknown error.\n" label)) + ) ; + raise (ExitWithError 1) + | `failure -> + let result = Client.Task.get_error_info ~rpc ~session_id ~self:task in + if result = [] then + marshal fd + (Command + (PrintStderr (Printf.sprintf "%s failed, unknown error\n" label)) + ) + else + raise (Api_errors.Server_error (List.hd result, List.tl result)) + | `cancelled -> + marshal fd (Command (PrintStderr (Printf.sprintf "%s cancelled\n" label))) ; + raise (ExitWithError 1) + | _ -> + marshal fd (Command (PrintStderr "Internal error\n")) ; + (* should never happen *) + raise (ExitWithError 1) + let download_file rpc session_id task fd filename uri label = marshal fd (Command (HttpGet (filename, uri))) ; let response = ref (Response Wait) in @@ -5484,34 +5515,8 @@ let download_file rpc session_id task fd filename uri label = wait_for_task_complete rpc session_id task ; (* Check the server status -- even if the client thinks it's ok, we need to check that the server does too. *) - match Client.Task.get_status ~rpc ~session_id ~self:task with - | `success -> - if ok then ( - if filename <> "" then - marshal fd (Command (Print (Printf.sprintf "%s succeeded" label))) - ) else ( - marshal fd - (Command - (PrintStderr (Printf.sprintf "%s failed, unknown error.\n" label)) - ) ; - raise (ExitWithError 1) - ) - | `failure -> - let result = Client.Task.get_error_info ~rpc ~session_id ~self:task in - if result = [] then - marshal fd - (Command - (PrintStderr (Printf.sprintf "%s failed, unknown error\n" label)) - ) - else - raise (Api_errors.Server_error (List.hd result, List.tl result)) - | `cancelled -> - marshal fd (Command (PrintStderr (Printf.sprintf "%s cancelled\n" label))) ; - raise (ExitWithError 1) - | _ -> - marshal fd (Command (PrintStderr "Internal error\n")) ; - (* should never happen *) - raise (ExitWithError 1) + let quiet_on_success = if filename = "" then true else false in + check_task_status ~rpc ~session_id ~task ~fd ~label ~ok ~quiet_on_success () let download_file_with_task fd rpc session_id filename uri query label task_name = @@ -5675,24 +5680,17 @@ let vm_import fd _printer rpc session_id params = in marshal fd (Command (Print (String.concat "," uuids))) -let blob_get fd _printer rpc session_id params = - let blob_uuid = List.assoc "uuid" params in - let blob_ref = Client.Blob.get_by_uuid ~rpc ~session_id ~uuid:blob_uuid in - let filename = List.assoc "filename" params in - let blobtask = +let command_in_task ~rpc ~session_id ~fd ~obj ~label ~quiet_on_success f = + let task = Client.Task.create ~rpc ~session_id - ~label:(Printf.sprintf "Obtaining blob, ref=%s" (Ref.string_of blob_ref)) + ~label:(Printf.sprintf "%s (ref=%s)" label (Ref.string_of obj)) ~description:"" in - Client.Task.set_progress ~rpc ~session_id ~self:blobtask ~value:(-1.0) ; - let bloburi = - Printf.sprintf "%s?session_id=%s&task_id=%s&ref=%s" Constants.blob_uri - (Ref.string_of session_id) (Ref.string_of blobtask) - (Ref.string_of blob_ref) - in + Client.Task.set_progress ~rpc ~session_id ~self:task ~value:(-1.0) ; + let command = f session_id task obj in finally (fun () -> - marshal fd (Command (HttpGet (filename, bloburi))) ; + marshal fd (Command command) ; let response = ref (Response Wait) in while !response = Response Wait do response := unmarshal fd @@ -5702,106 +5700,48 @@ let blob_get fd _printer rpc session_id params = | Response OK -> true | Response Failed -> - if Client.Task.get_progress ~rpc ~session_id ~self:blobtask < 0.0 - then - Client.Task.set_status ~rpc ~session_id ~self:blobtask - ~value:`failure ; + (* Need to check whether the thin cli managed to contact the server + * or not. If not, we need to mark the task as failed. + *) + if Client.Task.get_progress ~rpc ~session_id ~self:task < 0.0 then + Client.Task.set_status ~rpc ~session_id ~self:task ~value:`failure ; false | _ -> false in - wait_for_task_complete rpc session_id blobtask ; - (* if the client thinks it's ok, check that the server does too *) - match Client.Task.get_status ~rpc ~session_id ~self:blobtask with - | `success -> - if ok then - marshal fd (Command (Print "Blob get succeeded")) - else ( - marshal fd - (Command (PrintStderr "Blob get failed, unknown error.\n")) ; - raise (ExitWithError 1) - ) - | `failure -> - let result = - Client.Task.get_error_info ~rpc ~session_id ~self:blobtask - in - if result = [] then - marshal fd (Command (PrintStderr "Blob get failed, unknown error\n")) - else - raise (Api_errors.Server_error (List.hd result, List.tl result)) - | `cancelled -> - marshal fd (Command (PrintStderr "Blob get cancelled\n")) ; - raise (ExitWithError 1) - | _ -> - marshal fd (Command (PrintStderr "Internal error\n")) ; - (* should never happen *) - raise (ExitWithError 1) + wait_for_task_complete rpc session_id task ; + check_task_status ~rpc ~session_id ~task ~fd ~label ~ok ~quiet_on_success + () ) - (fun () -> Client.Task.destroy ~rpc ~session_id ~self:blobtask) + (fun () -> Client.Task.destroy ~rpc ~session_id ~self:task) + +let blob_uri ~session_id ~task ~blob = + let query = + [ + ("session_id", [Ref.string_of session_id]) + ; ("task_id", [Ref.string_of task]) + ; ("ref", [Ref.string_of blob]) + ] + in + Uri.make ~path:Constants.blob_uri ~query () |> Uri.to_string + +let blob_get fd _printer rpc session_id params = + let blob_uuid = List.assoc "uuid" params in + let blob_ref = Client.Blob.get_by_uuid ~rpc ~session_id ~uuid:blob_uuid in + let filename = List.assoc "filename" params in + command_in_task ~rpc ~session_id ~fd ~obj:blob_ref ~label:"GET blob" + ~quiet_on_success:false (fun session_id task blob -> + HttpGet (filename, blob_uri ~session_id ~task ~blob) + ) let blob_put fd _printer rpc session_id params = let blob_uuid = List.assoc "uuid" params in let blob_ref = Client.Blob.get_by_uuid ~rpc ~session_id ~uuid:blob_uuid in let filename = List.assoc "filename" params in - let blobtask = - Client.Task.create ~rpc ~session_id - ~label:(Printf.sprintf "Blob PUT, ref=%s" (Ref.string_of blob_ref)) - ~description:"" - in - Client.Task.set_progress ~rpc ~session_id ~self:blobtask ~value:(-1.0) ; - let bloburi = - Printf.sprintf "%s?session_id=%s&task_id=%s&ref=%s" Constants.blob_uri - (Ref.string_of session_id) (Ref.string_of blobtask) - (Ref.string_of blob_ref) - in - finally - (fun () -> - marshal fd (Command (HttpPut (filename, bloburi))) ; - let response = ref (Response Wait) in - while !response = Response Wait do - response := unmarshal fd - done ; - let ok = - match !response with - | Response OK -> - true - | Response Failed -> - if Client.Task.get_progress ~rpc ~session_id ~self:blobtask < 0.0 - then - Client.Task.set_status ~rpc ~session_id ~self:blobtask - ~value:`failure ; - false - | _ -> - false - in - wait_for_task_complete rpc session_id blobtask ; - (* if the client thinks it's ok, check that the server does too *) - match Client.Task.get_status ~rpc ~session_id ~self:blobtask with - | `success -> - if ok then - marshal fd (Command (Print "Blob put succeeded")) - else ( - marshal fd - (Command (PrintStderr "Blob put failed, unknown error.\n")) ; - raise (ExitWithError 1) - ) - | `failure -> - let result = - Client.Task.get_error_info ~rpc ~session_id ~self:blobtask - in - if result = [] then - marshal fd (Command (PrintStderr "Blob put failed, unknown error\n")) - else - raise (Api_errors.Server_error (List.hd result, List.tl result)) - | `cancelled -> - marshal fd (Command (PrintStderr "Blob put cancelled\n")) ; - raise (ExitWithError 1) - | _ -> - marshal fd (Command (PrintStderr "Internal error\n")) ; - (* should never happen *) - raise (ExitWithError 1) - ) - (fun () -> Client.Task.destroy ~rpc ~session_id ~self:blobtask) + command_in_task ~rpc ~session_id ~fd ~obj:blob_ref ~label:"PUT blob" + ~quiet_on_success:false (fun session_id task blob -> + HttpPut (filename, blob_uri ~session_id ~task ~blob) + ) let blob_create printer rpc session_id params = let name = List.assoc "name" params in @@ -7110,6 +7050,9 @@ let host_reset_server_certificate _printer rpc session_id params = params [] ) +let host_emergency_clear_mandatory_guidance _printer rpc session_id _params = + Client.Host.emergency_clear_mandatory_guidance ~rpc ~session_id + let host_management_reconfigure _printer rpc session_id params = let pif = Client.PIF.get_by_uuid ~rpc ~session_id ~uuid:(List.assoc "pif-uuid" params) @@ -7681,19 +7624,58 @@ let update_resync_host _printer rpc session_id params = let host = Client.Host.get_by_uuid ~rpc ~session_id ~uuid in Client.Pool_update.resync_host ~rpc ~session_id ~host -let host_apply_updates _printer rpc session_id params = +let get_avail_updates_uri ~session_id ~task ~host = + let query = + [ + ("session_id", [Ref.string_of session_id]) + ; ("task_id", [Ref.string_of task]) + ; ("host_refs", [Ref.string_of host]) + ] + in + Uri.make ~path:Constants.get_updates_uri ~query () |> Uri.to_string + +let print_avail_updates ~rpc ~session_id ~fd ~host = + command_in_task ~rpc ~session_id ~fd ~obj:host + ~label:"Print available updates for host" ~quiet_on_success:true + (fun session_id task host -> + PrintHttpGetJson (get_avail_updates_uri ~session_id ~task ~host) + ) + +let print_update_guidance ~rpc ~session_id ~fd ~host = + command_in_task ~rpc ~session_id ~fd ~obj:host + ~label:"Print update guidance for host" ~quiet_on_success:true + (fun session_id task host -> + PrintUpdateGuidance (get_avail_updates_uri ~session_id ~task ~host) + ) + +let host_apply_updates fd printer rpc session_id params = let hash = List.assoc "hash" params in - ignore - (do_host_op rpc session_id ~multiple:false - (fun _ host -> - let host = host.getref () in - Client.Host.apply_updates ~rpc ~session_id ~self:host ~hash - |> List.iter (fun l -> - _printer (Cli_printer.PMsg (String.concat "; " l)) - ) - ) - params ["hash"] + do_host_op rpc session_id ~multiple:false + (fun _ host -> + let host = host.getref () in + printer (Cli_printer.PMsg "Guidance of updates:") ; + print_update_guidance ~rpc ~session_id ~fd ~host ; + printer (Cli_printer.PMsg "Applying updates ...") ; + match Client.Host.apply_updates ~rpc ~session_id ~self:host ~hash with + | [] -> + printer (Cli_printer.PMsg "Updated.") + | warnings -> + printer (Cli_printer.PMsg "Updated with warnings:") ; + List.iter + (fun l -> printer (Cli_printer.PMsg (String.concat "; " l))) + warnings + ) + params ["hash"] + |> ignore + +let host_updates_show_available fd _printer rpc session_id params = + do_host_op rpc session_id ~multiple:false + (fun _ host -> + let host = host.getref () in + print_avail_updates ~rpc ~session_id ~fd ~host ) + params [] + |> ignore module SDN_controller = struct let introduce printer rpc session_id params = diff --git a/ocaml/xapi-cli-server/record_util.ml b/ocaml/xapi-cli-server/record_util.ml index 82ee16f8ce3..5332c2aee16 100644 --- a/ocaml/xapi-cli-server/record_util.ml +++ b/ocaml/xapi-cli-server/record_util.ml @@ -199,16 +199,24 @@ let host_operation_to_string = function "VM.migrate" | `apply_updates -> "apply_updates" + | `enable -> + "enable" let update_guidance_to_string = function | `reboot_host -> "reboot_host" | `reboot_host_on_livepatch_failure -> "reboot_host_on_livepatch_failure" + | `reboot_host_on_kernel_livepatch_failure -> + "reboot_host_on_kernel_livepatch_failure" + | `reboot_host_on_xen_livepatch_failure -> + "reboot_host_on_xen_livepatch_failure" | `restart_toolstack -> "restart_toolstack" | `restart_device_model -> "restart_device_model" + | `restart_vm -> + "restart_vm" let latest_synced_updates_applied_state_to_string = function | `yes -> diff --git a/ocaml/xapi-cli-server/records.ml b/ocaml/xapi-cli-server/records.ml index 5bc8838fb7e..6648d755876 100644 --- a/ocaml/xapi-cli-server/records.ml +++ b/ocaml/xapi-cli-server/records.ml @@ -2595,6 +2595,18 @@ let vm_record rpc session_id vm = ; make_field ~name:"vtpms" ~get:(fun () -> get_uuids_from_refs (x ()).API.vM_VTPMs) () + ; make_field ~name:"pending-guidances-recommended" + ~get:(fun () -> + map_and_concat Record_util.update_guidance_to_string + (x ()).API.vM_pending_guidances_recommended + ) + () + ; make_field ~name:"pending-guidances-full" + ~get:(fun () -> + map_and_concat Record_util.update_guidance_to_string + (x ()).API.vM_pending_guidances_full + ) + () ] } @@ -3249,6 +3261,21 @@ let host_record rpc session_id host = (x ()).API.host_latest_synced_updates_applied ) () + ; make_field ~name:"pending-guidances-recommended" + ~get:(fun () -> + map_and_concat Record_util.update_guidance_to_string + (x ()).API.host_pending_guidances_recommended + ) + () + ; make_field ~name:"pending-guidances-full" + ~get:(fun () -> + map_and_concat Record_util.update_guidance_to_string + (x ()).API.host_pending_guidances_full + ) + () + ; make_field ~name:"last-update-hash" + ~get:(fun () -> (x ()).API.host_last_update_hash) + () ] } diff --git a/ocaml/xapi-consts/api_errors.ml b/ocaml/xapi-consts/api_errors.ml index 09317e31074..43fff504a3d 100644 --- a/ocaml/xapi-consts/api_errors.ml +++ b/ocaml/xapi-consts/api_errors.ml @@ -1281,15 +1281,15 @@ let invalid_repository_domain_allowlist = "INVALID_REPOSITORY_DOMAIN_ALLOWLIST" let apply_livepatch_failed = "APPLY_LIVEPATCH_FAILED" -let updates_require_recommended_guidance = - "UPDATES_REQUIRE_RECOMMENDED_GUIDANCE" - -let update_guidance_changed = "UPDATE_GUIDANCE_CHANGED" - let invalid_update_sync_day = "INVALID_UPDATE_SYNC_DAY" let no_repositories_configured = "NO_REPOSITORIES_CONFIGURED" +let host_pending_mandatory_guidances_not_empty = + "HOST_PENDING_MANDATORY_GUIDANCE_NOT_EMPTY" + +let host_evacuation_is_required = "HOST_EVACUATION_IS_REQUIRED" + (* VTPMs *) let vtpm_max_amount_reached = "VTPM_MAX_AMOUNT_REACHED" diff --git a/ocaml/xapi/create_misc.ml b/ocaml/xapi/create_misc.ml index b10e5f020fa..a0d14ae94b9 100644 --- a/ocaml/xapi/create_misc.ml +++ b/ocaml/xapi/create_misc.ml @@ -292,7 +292,8 @@ and create_domain_zero_record ~__context ~domain_zero_ref (host_info : host_info ~version:0L ~generation_id:"" ~hardware_platform_version:0L ~has_vendor_device:false ~requires_reboot:false ~reference_label:"" ~domain_type:Xapi_globs.domain_zero_domain_type ~nVRAM:[] - ~pending_guidances:[] ~recommended_guidances:[] ; + ~pending_guidances:[] ~recommended_guidances:[] + ~pending_guidances_recommended:[] ~pending_guidances_full:[] ; ensure_domain_zero_metrics_record ~__context ~domain_zero_ref host_info ; Db.Host.set_control_domain ~__context ~self:localhost ~value:domain_zero_ref ; Xapi_vm_helpers.update_memory_overhead ~__context ~vm:domain_zero_ref diff --git a/ocaml/xapi/dbsync_slave.ml b/ocaml/xapi/dbsync_slave.ml index 594cdd83075..84af29bbf7f 100644 --- a/ocaml/xapi/dbsync_slave.ml +++ b/ocaml/xapi/dbsync_slave.ml @@ -232,6 +232,21 @@ let resynchronise_pif_params ~__context = (* Ensure that all DHCP PIFs have their IP address updated in the DB *) Helpers.update_pif_addresses ~__context +let remove_pending_guidances ~__context = + let localhost = Helpers.get_localhost ~__context in + Xapi_host_helpers.remove_pending_guidance ~__context ~self:localhost + ~value:`restart_toolstack ; + if !Xapi_globs.on_system_boot then ( + Xapi_host_helpers.remove_pending_guidance ~__context ~self:localhost + ~value:`reboot_host ; + Xapi_host_helpers.remove_pending_guidance ~__context ~self:localhost + ~value:`reboot_host_on_livepatch_failure ; + Xapi_host_helpers.remove_pending_guidance ~__context ~self:localhost + ~value:`reboot_host_on_kernel_livepatch_failure ; + Xapi_host_helpers.remove_pending_guidance ~__context ~self:localhost + ~value:`reboot_host_on_xen_livepatch_failure + ) + (** Update the database to reflect current state. Called for both start of day and after an agent restart. *) let update_env __context sync_keys = @@ -351,4 +366,6 @@ let update_env __context sync_keys = switched_sync Xapi_globs.sync_chipset_info (fun () -> Create_misc.create_chipset_info ~__context info ) ; - switched_sync Xapi_globs.sync_gpus (fun () -> Xapi_pgpu.update_gpus ~__context) + switched_sync Xapi_globs.sync_gpus (fun () -> Xapi_pgpu.update_gpus ~__context) ; + + remove_pending_guidances ~__context diff --git a/ocaml/xapi/message_forwarding.ml b/ocaml/xapi/message_forwarding.ml index 596e8815934..b2eb86c805d 100644 --- a/ocaml/xapi/message_forwarding.ml +++ b/ocaml/xapi/message_forwarding.ml @@ -3279,8 +3279,11 @@ functor let enable ~__context ~host = info "Host.enable: host = '%s'" (host_uuid ~__context host) ; let local_fn = Local.Host.enable ~host in - do_op_on ~local_fn ~__context ~host (fun session_id rpc -> - Client.Host.enable ~rpc ~session_id ~host + Xapi_host_helpers.with_host_operation ~__context ~self:host + ~doc:"Host.enable" ~op:`enable (fun () -> + do_op_on ~local_fn ~__context ~host (fun session_id rpc -> + Client.Host.enable ~rpc ~session_id ~host + ) ) ; Xapi_host_helpers.update_allowed_operations ~__context ~self:host @@ -4050,6 +4053,10 @@ functor do_op_on ~local_fn ~__context ~host:self (fun session_id rpc -> Client.Host.set_https_only ~rpc ~session_id ~self ~value ) + + let emergency_clear_mandatory_guidance ~__context = + info "Host.emergency_clear_mandatory_guidance" ; + Local.Host.emergency_clear_mandatory_guidance ~__context end module Host_crashdump = struct diff --git a/ocaml/xapi/repository.ml b/ocaml/xapi/repository.ml index c80f51ad41a..bd63984c0a1 100644 --- a/ocaml/xapi/repository.ml +++ b/ocaml/xapi/repository.ml @@ -532,6 +532,20 @@ let get_repository_handler (req : Http.Request.t) s _ = Http_svr.response_forbidden ~req s ) +let consolidate_updates_of_hosts ~repository_name ~updates_info ~hosts = + Hashtbl.fold + (fun host updates_of_host (acc1, acc2) -> + if List.mem host hosts then + let updates_of_host, upd_ids = + consolidate_updates_of_host ~repository_name ~updates_info + (Ref.string_of host) updates_of_host + in + (updates_of_host :: acc1, UpdateIdSet.union upd_ids acc2) + else + (acc1, acc2) + ) + updates_in_cache ([], UpdateIdSet.empty) + let get_pool_updates_in_json ~__context ~hosts = try let repository = get_single_enabled_update_repository ~__context in @@ -543,18 +557,7 @@ let get_pool_updates_in_json ~__context ~hosts = parse_updateinfo ~__context ~self:repository ~check:true in let updates_of_hosts, ids_of_updates = - Hashtbl.fold - (fun host updates_of_host (acc1, acc2) -> - if List.mem host hosts then - let updates_of_host, upd_ids = - consolidate_updates_of_host ~repository_name ~updates_info - (Ref.string_of host) updates_of_host - in - (updates_of_host :: acc1, UpdateIdSet.union upd_ids acc2) - else - (acc1, acc2) - ) - updates_in_cache ([], UpdateIdSet.empty) + consolidate_updates_of_hosts ~repository_name ~updates_info ~hosts in let lps = updates_of_hosts @@ -623,47 +626,6 @@ let apply_livepatch ~__context ~host:_ ~component ~base_build_id ~base_version error "%s" msg ; raise Api_errors.(Server_error (internal_error, [msg])) -let set_restart_device_models ~__context ~host = - (* Set pending restart device models of all running HVM VMs on the host *) - do_with_device_models ~__context ~host @@ fun (ref, record) -> - match - (record.API.vM_power_state, Helpers.has_qemu_currently ~__context ~self:ref) - with - | `Running, true | `Paused, true -> - Db.VM.set_pending_guidances ~__context ~self:ref - ~value:[`restart_device_model] ; - None - | _ -> - (* No device models are running for this VM *) - None - -let set_guidances ~__context ~host ~guidances ~db_set = - let open Guidance in - guidances - |> List.fold_left - (fun acc g -> - match g with - | RebootHost -> - `reboot_host :: acc - | RestartToolstack -> - `restart_toolstack :: acc - | RestartDeviceModel -> - set_restart_device_models ~__context ~host ; - acc - | RebootHostOnLivePatchFailure -> - `reboot_host_on_livepatch_failure :: acc - | _ -> - warn "Unsupported pending guidance %s, ignoring it." - (Guidance.to_string g) ; - acc - ) - [] - |> fun gs -> db_set ~__context ~self:host ~value:gs - -let set_pending_guidances ~__context ~host ~guidances = - set_guidances ~__context ~host ~guidances - ~db_set:Db.Host.set_pending_guidances - let apply_livepatches' ~__context ~host ~livepatches = List.partition_map (fun (lp, lps) -> @@ -695,18 +657,7 @@ let apply_livepatches' ~__context ~host ~livepatches = ) livepatches -let apply_updates' ~__context ~host ~updates_info ~livepatches ~acc_rpm_updates - = - (* This function runs on coordinator host *) - (* Install RPM updates firstly *) - Helpers.call_api_functions ~__context (fun rpc session_id -> - Client.Client.Repository.apply ~rpc ~session_id ~host - ) ; - (* Apply live patches with best effort *) - let successful_livepatches, failed_livepatches = - apply_livepatches' ~__context ~host ~livepatches - in - (* Update states in cache with best effort as well *) +let update_cache ~host ~failed_livepatches = Hashtbl.replace updates_in_cache host (`Assoc [ @@ -720,45 +671,115 @@ let apply_updates' ~__context ~host ~updates_info ~livepatches ~acc_rpm_updates ) ) ] - ) ; - (* Evaluate guidances *) - let guidances = - let guidances' = - (* EvacuateHost will be applied before applying updates *) - eval_guidances ~updates_info ~updates:acc_rpm_updates ~kind:Recommended - ~livepatches:successful_livepatches ~failed_livepatches - |> List.filter (fun g -> g <> Guidance.EvacuateHost) - |> fun l -> merge_with_unapplied_guidances ~__context ~host ~guidances:l + ) + +let maybe_set_restart_for_all_vms ~__context ~updates_of_hosts = + let open Guidance in + let add_restart_to_vms ~__context ~vms ~kind = + let ( let* ) = Option.bind in + let* op = + match kind with + | Mandatory -> + Some Db.VM.add_pending_guidances + | Recommended -> + Some Db.VM.add_pending_guidances_recommended + | Full -> + Some Db.VM.add_pending_guidances_full + | Livepatch -> + None in - GuidanceSet.assert_valid_guidances guidances' ; - match failed_livepatches with - | [] -> - guidances' - | _ :: _ -> - (* There is(are) livepatch failure(s): - * the host should not be rebooted, and - * an extra pending guidance 'RebootHostOnLivePatchFailure' should be set. - *) - guidances' - |> List.filter (fun g -> g <> Guidance.RebootHost) - |> List.cons Guidance.RebootHostOnLivePatchFailure + Some + (List.iter + (fun self -> + debug "add RestartVM for VM %s" (Ref.string_of self) ; + op ~__context ~self ~value:`restart_vm + ) + vms + ) in - List.iter - (fun g -> debug "pending_guidance: %s" (Guidance.to_string g)) - guidances ; - set_pending_guidances ~__context ~host ~guidances ; - ( guidances - , List.map - (fun (lp, _) -> - [Api_errors.apply_livepatch_failed; LivePatch.to_string lp] + if updates_of_hosts <> [] then + let vms = + Db.VM.get_all ~__context + |> List.filter (fun self -> + Db.VM.get_power_state ~__context ~self <> `Halted + && not (Db.VM.get_is_control_domain ~__context ~self) + ) + in + (* fold each guidance kind from all hosts *) + updates_of_hosts + |> List.concat_map (fun HostUpdates.{guidance; _} -> guidance) + |> List.fold_left + (fun acc (kind, l) -> + if List.mem RestartVM l && not (List.mem kind acc) then + kind :: acc + else + acc + ) + [] + |> List.iter (fun kind -> + (* set RestartVM for all VMs if it is presented from at least one host *) + debug "add RestartVM for all VMs' pending %s guidance list" + (kind_to_string kind) ; + add_restart_to_vms ~__context ~vms ~kind |> ignore + ) + +let apply_updates' ~__context ~host ~updates_info ~livepatches ~acc_rpm_updates + = + (* This function runs on coordinator host *) + let open Guidance in + let guidance' = + reduce_guidance ~updates_info ~updates:acc_rpm_updates ~livepatches + in + let mandatory = + match List.assoc_opt Mandatory guidance' with + | Some tasks -> + tasks + | None -> + warn "No mandatory guidance found. Ignore it." ; + [] + in + assert_host_evacuation_if_required ~__context ~host ~mandatory ; + let guidance = + (* EvacuateHost should be carried out before host.apply_updates *) + guidance' + |> List.map (fun (k, l) -> (k, List.filter (fun x -> x <> EvacuateHost) l)) + in + (* Install RPM updates *) + Helpers.call_api_functions ~__context (fun rpc session_id -> + Client.Client.Repository.apply ~rpc ~session_id ~host + ) ; + (* Always apply livepatches even if host will reboot *) + let applied_livepatches, failed_livepatches = + apply_livepatches' ~__context ~host ~livepatches + in + (* Update states in cache *) + update_cache ~host ~failed_livepatches ; + (* Set pending guidance lists *) + let set_guidance ~kind ~coming = + List.iter + (fun g -> + debug "setting %s pending guidance: %s" (kind_to_string kind) + (to_string g) ) - failed_livepatches - ) + coming ; + let ops = get_ops_of_pending ~__context ~host ~kind in + set_pending_guidances ~ops ~coming + in + guidance |> List.iter (fun (kind, coming) -> set_guidance ~kind ~coming) ; + let get_livepatch_component (lp, _) = lp.LivePatch.component in + let applied = List.map get_livepatch_component applied_livepatches in + let failed = List.map get_livepatch_component failed_livepatches in + update_livepatch_failure_guidance ~__context ~host ~applied ~failed ; + List.map + (fun (lp, _) -> [Api_errors.apply_livepatch_failed; LivePatch.to_string lp]) + failed_livepatches let apply_updates ~__context ~host ~hash = (* This function runs on coordinator host *) + assert_no_host_pending_mandatory_guidance ~__context ~host ; try let repository = get_single_enabled_update_repository ~__context in + let repository_name = get_repository_name ~__context ~self:repository in if hash = "" || hash <> Db.Repository.get_hash ~__context ~self:repository then raise Api_errors.(Server_error (updateinfo_hash_mismatch, [])) ; @@ -766,6 +787,17 @@ let apply_updates ~__context ~host ~hash = let updates_info = parse_updateinfo ~__context ~self:repository ~check:true in + let updates_of_hosts = + if Helpers.is_pool_master ~__context ~host then ( + (* save available updates before applying on coordinator *) + if Hashtbl.length updates_in_cache = 0 then + set_available_updates ~__context |> ignore ; + let hosts = Db.Host.get_all ~__context in + consolidate_updates_of_hosts ~repository_name ~updates_info ~hosts + |> fst + ) else + [] + in let host_updates = http_get_host_updates_in_json ~__context ~host ~installed:true in @@ -778,20 +810,21 @@ let apply_updates ~__context ~host ~hash = retrieve_livepatches_from_updateinfo ~updates_info ~updates:host_updates in - match (rpm_updates, livepatches) with - | [], [] -> - let host' = Ref.string_of host in - info "Host ref='%s' is already up to date." host' ; - ([], []) - | _ -> - let repository_name = - get_repository_name ~__context ~self:repository - in - let _, acc_rpm_updates = - merge_updates ~repository_name ~updates:host_updates - in - apply_updates' ~__context ~host ~updates_info ~livepatches - ~acc_rpm_updates + let ret = + match (rpm_updates, livepatches) with + | [], [] -> + let host' = Ref.string_of host in + info "Host ref='%s' is already up to date." host' ; + [] + | _ -> + let _, acc_rpm_updates = + merge_updates ~repository_name ~updates:host_updates + in + apply_updates' ~__context ~host ~updates_info ~livepatches + ~acc_rpm_updates + in + maybe_set_restart_for_all_vms ~__context ~updates_of_hosts ; + ret ) with | Api_errors.(Server_error (code, _)) as e diff --git a/ocaml/xapi/repository.mli b/ocaml/xapi/repository.mli index 43ea34cbe4f..8b8ee7e09cd 100644 --- a/ocaml/xapi/repository.mli +++ b/ocaml/xapi/repository.mli @@ -64,7 +64,7 @@ val apply_updates : __context:Context.t -> host:[`host] API.Ref.t -> hash:string - -> Updateinfo.Guidance.t list * string list list + -> string list list val set_available_updates : __context:Context.t -> string diff --git a/ocaml/xapi/repository_helpers.ml b/ocaml/xapi/repository_helpers.ml index 7846ff4777e..f67e8647822 100644 --- a/ocaml/xapi/repository_helpers.ml +++ b/ocaml/xapi/repository_helpers.ml @@ -99,54 +99,38 @@ module GuidanceSet = struct include GuidanceSet' open Guidance - let eq l s = equal (of_list l) (of_list s) - - let eq_set1 = eq [EvacuateHost; RestartToolstack] - - let eq_set2 = eq [RestartDeviceModel; RestartToolstack] - - let error_msg l = - Printf.sprintf "Found wrong guidance(s): %s" - (String.concat ";" (List.map to_string l)) - - let assert_valid_guidances = function - | [] - | [RebootHost] - | [EvacuateHost] - | [RestartToolstack] - | [RestartDeviceModel] -> - () - | l when eq_set1 l -> - (* EvacuateHost and RestartToolstack *) - () - | l when eq_set2 l -> - (* RestartDeviceModel and RestartToolstack *) - () - | l -> - let msg = error_msg l in - raise Api_errors.(Server_error (internal_error, [msg])) - let precedences = [ (RebootHost, of_list [RestartToolstack; EvacuateHost; RestartDeviceModel]) ; (EvacuateHost, of_list [RestartDeviceModel]) + ; (RestartVM, of_list [RestartDeviceModel]) ] - let resort_guidances ~remove_evacuations gs = - let gs' = + (** reduce the set [gs] by sorting partially with the defined [precedences] *) + let resort gs = + List.fold_left + (fun acc (higher, lowers) -> + if mem higher acc then + diff acc lowers + else + acc + ) + gs precedences + + (** remove the guidances which are in s1 from s2. *) + let reduce s1 s2 = diff (resort (union s1 s2)) s1 + + (** for each set in the list, remove the guidances which are in previous sets from it. *) + let reduce_cascaded_list l = + let _, reduced_list_in_reverse = List.fold_left - (fun acc (higher, lowers) -> - if mem higher acc then - diff acc lowers - else - acc + (fun (acc_set, acc_list) (k, s) -> + let reduced = reduce acc_set s in + (union s acc_set, (k, reduced) :: acc_list) ) - gs precedences + (empty, []) l in - if remove_evacuations then - remove EvacuateHost gs' - else - gs' + List.rev reduced_list_in_reverse end let create_repository_record ~__context ~name_label ~name_description @@ -559,34 +543,31 @@ let get_updates_from_updateinfo ~__context repositories = in new_updates @ updates +let is_applicable ~update (applicability : Applicability.t) = + let open Update in + match + (update.name = applicability.name, update.arch = applicability.arch) + with + | true, true -> ( + match (update.old_epoch, update.old_version, update.old_release) with + | Some old_epoch, Some old_version, Some old_release -> + Applicability.eval ~epoch:old_epoch ~version:old_version + ~release:old_release ~applicability + | _ -> + warn "No installed epoch, version or release for package %s.%s" + update.name update.arch ; + false + ) + | _ -> + false + let eval_guidance_for_one_update ~updates_info ~update ~kind - ~upd_ids_of_livepatches ~upd_ids_of_failed_livepatches = + ~upd_ids_of_livepatches = let open Update in match update.update_id with | Some upd_id -> ( match List.assoc_opt upd_id updates_info with | Some updateinfo -> ( - let is_applicable (a : Applicability.t) = - match - ( update.name = a.Applicability.name - , update.arch = a.Applicability.arch - ) - with - | true, true -> ( - match - (update.old_epoch, update.old_version, update.old_release) - with - | Some old_epoch, Some old_version, Some old_release -> - Applicability.eval ~epoch:old_epoch ~version:old_version - ~release:old_release ~applicability:a - | _ -> - warn "No installed epoch, version or release for package %s.%s" - update.name update.arch ; - false - ) - | _ -> - false - in let pkg_str = Printf.sprintf "package %s.%s in update %s" update.name update.arch upd_id @@ -595,72 +576,77 @@ let eval_guidance_for_one_update ~updates_info ~update ~kind Printf.sprintf "Evaluating applicability for %s returned '%s'" pkg_str (string_of_bool r) in + let str_of_guidances guidances = + String.concat ";" (List.map Guidance.to_string guidances) + in let apps = updateinfo.UpdateInfo.guidance_applicabilities in - match (List.exists is_applicable apps, apps) with + match (List.exists (is_applicable ~update) apps, apps) with | true, _ | false, [] -> ( debug "%s" (dbg_msg true) ; + let open Guidance in + let open UpdateInfo in match kind with - | Guidance.Absolute -> - updateinfo.UpdateInfo.abs_guidance - | Guidance.Recommended -> ( - match - ( UpdateIdSet.mem upd_id upd_ids_of_livepatches - , UpdateIdSet.mem upd_id upd_ids_of_failed_livepatches - ) - with - | _, true -> - (* The update contains a failed livepatch. No guidance should be picked up. *) - debug - "%s doesn't contribute guidance due to a livepatch failure" - pkg_str ; - None - | true, false -> - (* The update has an applicable/successful livepatch. - * Using the livepatch guidance. - *) - let g = updateinfo.UpdateInfo.livepatch_guidance in - debug "%s provides livepatch guidance %s" pkg_str - (Option.value (Option.map Guidance.to_string g) ~default:"") ; - g - | false, false -> - let g = updateinfo.UpdateInfo.rec_guidance in - debug "%s provides recommended guidance %s" pkg_str - (Option.value (Option.map Guidance.to_string g) ~default:"") ; - g - ) + | Livepatch -> + (* Livepatch should not be evaluated directly *) + [] + | Full | Mandatory -> + let gs = get_guidances_of_kind ~kind updateinfo in + debug "%s contributes to %s guidances [%s]" pkg_str + (kind_to_string kind) (str_of_guidances gs) ; + gs + | Recommended -> + let gs = + match UpdateIdSet.mem upd_id upd_ids_of_livepatches with + | true -> + debug "use livepatch guidance of %s" pkg_str ; + get_guidances_of_kind ~kind:Livepatch updateinfo + | false -> + get_guidances_of_kind ~kind:Recommended updateinfo + in + debug "%s contributes to recommended guidances [%s]" pkg_str + (str_of_guidances gs) ; + gs ) | _ -> debug "%s" (dbg_msg false) ; - None + [] ) | None -> warn "Can't find update ID %s from updateinfo.xml for update %s.%s" upd_id update.name update.arch ; - None + [] ) | None -> warn "Ignore evaluating against package %s.%s as its update ID is missing" update.name update.arch ; - None + [] (* In case that the RPM in an update has been installed (including livepatch file), * but the livepatch has not been applied. * In other words, this RPM update will not appear in parameter [updates] of * function [eval_guidances], but the livepatch in it is still applicable. *) -let append_livepatch_guidances ~updates_info ~upd_ids_of_livepatches guidances = +let append_livepatch_guidance ~updates_info ~upd_ids_of_livepatches + guidance_tasks = + let ( let* ) = Option.bind in UpdateIdSet.fold (fun upd_id acc -> - match List.assoc_opt upd_id updates_info with - | Some UpdateInfo.{livepatch_guidance= Some g; _} -> - GuidanceSet.add g acc - | _ -> + let get_livepatch_guidance () = + let* updateinfo = List.assoc_opt upd_id updates_info in + let* l = + List.assoc_opt Guidance.Livepatch updateinfo.UpdateInfo.guidance + in + Some (GuidanceSet.of_list l) + in + match get_livepatch_guidance () with + | Some s -> + GuidanceSet.union s acc + | None -> acc ) - upd_ids_of_livepatches guidances + upd_ids_of_livepatches guidance_tasks -let eval_guidances ~updates_info ~updates ~kind ~livepatches ~failed_livepatches - = +let eval_guidances ~updates_info ~updates ~kind ~livepatches = let extract_upd_ids lps = List.fold_left (fun acc (_, lps) -> @@ -671,32 +657,22 @@ let eval_guidances ~updates_info ~updates ~kind ~livepatches ~failed_livepatches UpdateIdSet.empty lps in let upd_ids_of_livepatches = extract_upd_ids livepatches in - let upd_ids_of_failed_livepatches = extract_upd_ids failed_livepatches in List.fold_left (fun acc u -> - match - eval_guidance_for_one_update ~updates_info ~update:u ~kind - ~upd_ids_of_livepatches ~upd_ids_of_failed_livepatches - with - | Some g -> - GuidanceSet.add g acc - | None -> - acc + eval_guidance_for_one_update ~updates_info ~update:u ~kind + ~upd_ids_of_livepatches + |> GuidanceSet.of_list + |> GuidanceSet.union acc ) GuidanceSet.empty updates - |> append_livepatch_guidances ~updates_info ~upd_ids_of_livepatches - |> GuidanceSet.resort_guidances ~remove_evacuations:(kind = Guidance.Absolute) - |> GuidanceSet.elements - -let merge_with_unapplied_guidances ~__context ~host ~guidances = - let open GuidanceSet in - Db.Host.get_pending_guidances ~__context ~self:host - |> List.map (fun g -> Guidance.of_update_guidance g) - |> List.filter (fun g -> g <> Guidance.RebootHostOnLivePatchFailure) - |> of_list - |> union (of_list guidances) - |> resort_guidances ~remove_evacuations:false - |> elements + |> (fun l -> + match kind with + | Recommended -> + append_livepatch_guidance ~updates_info ~upd_ids_of_livepatches l + | _ -> + l + ) + |> GuidanceSet.resort let repoquery_sep = ":|" @@ -1248,6 +1224,16 @@ let merge_livepatches ~livepatches = ) (UpdateIdSet.empty, []) +let reduce_guidance ~updates_info ~updates ~livepatches = + let open Guidance in + (* The order does matter with the following reducing *) + [Mandatory; Recommended; Full] + |> List.map (fun kind -> + (kind, eval_guidances ~updates_info ~updates ~kind ~livepatches) + ) + |> GuidanceSet.reduce_cascaded_list + |> List.map (fun (kind, s) -> (kind, GuidanceSet.elements s)) + let consolidate_updates_of_host ~repository_name ~updates_info host updates_of_host = let latest_updates = @@ -1281,35 +1267,19 @@ let consolidate_updates_of_host ~repository_name ~updates_info host let livepatches = retrieve_livepatches_from_updateinfo ~updates_info ~updates:updates_of_host in - let rec_guidances = - eval_guidances ~updates_info ~updates ~kind:Recommended ~livepatches - ~failed_livepatches:[] - in - let abs_guidances = - eval_guidances ~updates_info ~updates ~kind:Absolute ~livepatches:[] - ~failed_livepatches:[] - |> List.filter (fun g -> not (List.mem g rec_guidances)) - in - let upd_ids_of_livepatches, lps = - if List.mem Guidance.RebootHost rec_guidances then - (* Any livepatches should not be applied if packages updates require RebootHost *) - (UpdateIdSet.empty, []) - else - merge_livepatches ~livepatches - in + let guidance = reduce_guidance ~updates_info ~updates ~livepatches in + let upd_ids_of_livepatches, lps = merge_livepatches ~livepatches in let upd_ids = UpdateIdSet.union ids_of_updates upd_ids_of_livepatches in let host_updates = HostUpdates. { host - ; rec_guidances - ; abs_guidances + ; guidance ; rpms ; update_ids= UpdateIdSet.elements upd_ids ; livepatches= lps } in - (host_updates, upd_ids) let append_by_key l k v = @@ -1372,15 +1342,314 @@ let prune_updateinfo_for_livepatches livepatches updateinfo = in {updateinfo with livepatches= lps} -let do_with_device_models ~__context ~host f = - (* Call f with device models of all running HVM VMs on the host *) - Db.Host.get_resident_VMs ~__context ~self:host - |> List.map (fun self -> (self, Db.VM.get_record ~__context ~self)) - |> List.filter (fun (_, record) -> not record.API.vM_is_control_domain) - |> List.filter_map f - |> function - | [] -> +let do_with_host_pending_guidances ~op guidances = + List.iter + (fun g -> + match Guidance.to_pending_guidance g with + | ( Some `restart_toolstack + | Some `reboot_host + | Some `reboot_host_on_xen_livepatch_failure + | Some `reboot_host_on_kernel_livepatch_failure + | Some `reboot_host_on_livepatch_failure ) as g' -> + Option.iter op g' + | _ -> + () + ) + guidances + +let do_with_vm_pending_guidances ~op ~vm guidances = + List.iter + (fun g -> + match Guidance.to_pending_guidance g with + | Some `restart_device_model -> + op vm `restart_device_model + | Some `restart_vm -> + op vm `restart_vm + | _ -> + () + ) + guidances + +let merge_with_pending_guidances ~pending ~coming = + let open GuidanceSet in + let pending = of_list pending in + let unioned = union pending (of_list coming) |> remove EvacuateHost in + (diff unioned pending |> elements, diff pending unioned |> elements) + +let is_livepatch_failure = function + | Guidance.RebootHostOnLivePatchFailure + | Guidance.RebootHostOnXenLivePatchFailure + | Guidance.RebootHostOnKernelLivePatchFailure -> + true + | _ -> + false + +type pending_ops = { + host_get: unit -> Guidance.t list + ; host_add: + [ `reboot_host + | `reboot_host_on_livepatch_failure + | `restart_toolstack + | `reboot_host_on_xen_livepatch_failure + | `reboot_host_on_kernel_livepatch_failure ] + -> unit + ; host_remove: + [ `reboot_host + | `reboot_host_on_livepatch_failure + | `restart_toolstack + | `reboot_host_on_xen_livepatch_failure + | `reboot_host_on_kernel_livepatch_failure ] + -> unit + ; vms_get: unit -> (string * Guidance.t list) list + ; vm_add: string -> [`restart_device_model | `restart_vm] -> unit + ; vm_remove: string -> [`restart_device_model | `restart_vm] -> unit +} + +let get_ops_of_pending ~__context ~host ~kind = + let get_pending_guidances_of_host ~db_get = + db_get ~__context ~self:host |> List.map Guidance.of_pending_guidance + in + let get_pending_guidances_of_vms ~db_get = + Db.Host.get_resident_VMs ~__context ~self:host + |> List.filter (fun self -> + not (Db.VM.get_is_control_domain ~__context ~self) + ) + |> List.map (fun vm_ref -> + let pending_guidances = + db_get ~__context ~self:vm_ref + |> List.map Guidance.of_pending_guidance + in + (Ref.string_of vm_ref, pending_guidances) + ) + in + let is_vm_applicable ~self = function + | `restart_device_model -> + Helpers.has_qemu_currently ~__context ~self + | `restart_vm -> + (* RestartVM will be set in host.apply_updates when updating the coordinator. + * To avoid confusions, not setting it here. + *) + false + in + match kind with + | Guidance.Mandatory -> + let host_get () = + get_pending_guidances_of_host ~db_get:Db.Host.get_pending_guidances + in + let host_add value = + Db.Host.add_pending_guidances ~__context ~self:host ~value + in + let host_remove value = + Db.Host.remove_pending_guidances ~__context ~self:host ~value + in + let vms_get () = + get_pending_guidances_of_vms ~db_get:Db.VM.get_pending_guidances + in + let vm_add vm value = + let self = Ref.of_string vm in + if is_vm_applicable ~self value then + Db.VM.add_pending_guidances ~__context ~self:(Ref.of_string vm) ~value + in + let vm_remove vm value = + Db.VM.remove_pending_guidances ~__context ~self:(Ref.of_string vm) + ~value + in + {host_get; host_add; host_remove; vms_get; vm_add; vm_remove} + | Guidance.Recommended -> + let host_get () = + get_pending_guidances_of_host + ~db_get:Db.Host.get_pending_guidances_recommended + in + let host_add value = + Db.Host.add_pending_guidances_recommended ~__context ~self:host ~value + in + let host_remove value = + Db.Host.remove_pending_guidances_recommended ~__context ~self:host + ~value + in + let vms_get () = + get_pending_guidances_of_vms + ~db_get:Db.VM.get_pending_guidances_recommended + in + let vm_add vm value = + let self = Ref.of_string vm in + if is_vm_applicable ~self value then + Db.VM.add_pending_guidances_recommended ~__context ~self ~value + in + let vm_remove vm value = + Db.VM.remove_pending_guidances_recommended ~__context + ~self:(Ref.of_string vm) ~value + in + {host_get; host_add; host_remove; vms_get; vm_add; vm_remove} + | Guidance.Full -> + let host_get () = + get_pending_guidances_of_host ~db_get:Db.Host.get_pending_guidances_full + in + let host_add value = + Db.Host.add_pending_guidances_full ~__context ~self:host ~value + in + let host_remove value = + Db.Host.remove_pending_guidances_full ~__context ~self:host ~value + in + let vms_get () = + get_pending_guidances_of_vms ~db_get:Db.VM.get_pending_guidances_full + in + let vm_add vm value = + let self = Ref.of_string vm in + if is_vm_applicable ~self value then + Db.VM.add_pending_guidances_full ~__context ~self ~value + in + let vm_remove vm value = + Db.VM.remove_pending_guidances_full ~__context ~self:(Ref.of_string vm) + ~value + in + {host_get; host_add; host_remove; vms_get; vm_add; vm_remove} + | Guidance.Livepatch -> + raise + Api_errors.( + Server_error + (internal_error, ["No pending operations for Livepatch guidance"]) + ) + +let set_pending_guidances ~ops ~coming = + let pending_of_host = + ops.host_get () |> List.filter (fun x -> not (is_livepatch_failure x)) + in + let to_be_added, to_be_removed = + merge_with_pending_guidances ~pending:pending_of_host ~coming + in + do_with_host_pending_guidances ~op:ops.host_remove to_be_removed ; + do_with_host_pending_guidances ~op:ops.host_add to_be_added ; + + ops.vms_get () + |> List.map (fun (vm_ref_str, pending_of_vm) -> + let pending = List.append pending_of_host pending_of_vm in + (vm_ref_str, merge_with_pending_guidances ~pending ~coming) + ) + |> List.iter (fun (vm_ref_str, (to_be_added, to_be_removed)) -> + do_with_vm_pending_guidances ~op:ops.vm_remove ~vm:vm_ref_str + to_be_removed ; + do_with_vm_pending_guidances ~op:ops.vm_add ~vm:vm_ref_str to_be_added + ) + +let failure_of_livepatch_component = function + | Livepatch.Xen -> + Guidance.RebootHostOnXenLivePatchFailure + | Livepatch.Kernel -> + Guidance.RebootHostOnKernelLivePatchFailure + +let component_of_livepatch_failure = function + | Guidance.RebootHostOnXenLivePatchFailure -> + Some Livepatch.Xen + | Guidance.RebootHostOnKernelLivePatchFailure -> + Some Livepatch.Kernel + | _ -> + None + +let merge_livepatch_failures ~previous_failures ~applied ~failed = + let current_failures = List.map failure_of_livepatch_component failed in + (* Determine how to deal with previous livepatch failures *) + List.fold_left + (fun acc_fails prev_fail -> + match component_of_livepatch_failure prev_fail with + | Some prev_failed_component -> ( + let comp_str = Livepatch.string_of_component prev_failed_component in + match + ( List.mem prev_fail current_failures + , List.mem prev_failed_component applied + ) + with + | true, false -> + debug "%s Livepatch failed again." comp_str ; + acc_fails + | false, true -> + (* applied in this update. No changes on current failures *) + debug + "%s Livepatch (failed in previous updates) has been applied." + comp_str ; + acc_fails + | true, true -> + (* Impossible case: a component is in both failed and applied *) + warn "%s Livepatch shouldn't be in both applied and failed lists." + comp_str ; + acc_fails + | false, false -> + (* Didn't try in this update. Keep previous failure in current list *) + debug "%s Livepatch failed in previous updates." comp_str ; + prev_fail :: acc_fails + ) + | None -> + warn "Unknown livepatch failure." ; + acc_fails + ) + current_failures previous_failures + |> fun failures -> + let open GuidanceSet in + let to_be_removed = + diff (of_list previous_failures) (of_list failures) |> elements + in + let to_be_added = + diff (of_list failures) (of_list previous_failures) |> elements + in + (to_be_removed, to_be_added) + +let update_livepatch_failure_guidance ~__context ~host ~applied ~failed = + let previous_failures = + Db.Host.get_pending_guidances_recommended ~__context ~self:host + |> List.map Guidance.of_pending_guidance + |> List.filter is_livepatch_failure + in + let to_be_removed, to_be_added = + merge_livepatch_failures ~previous_failures ~applied ~failed + in + (* The livepatch failure guidance is in host pending recommended list *) + let host_remove value = + Db.Host.remove_pending_guidances_recommended ~__context ~self:host ~value + in + let host_add value = + Db.Host.add_pending_guidances_recommended ~__context ~self:host ~value + in + List.iter host_remove + (List.filter_map Guidance.to_pending_guidance to_be_removed) ; + List.iter host_add (List.filter_map Guidance.to_pending_guidance to_be_added) + +let assert_no_host_pending_mandatory_guidance ~__context ~host = + let host_pending_mandatory_guidances = + Db.Host.get_pending_guidances ~__context ~self:host + in + if host_pending_mandatory_guidances <> [] then ( + error "%s: %d mandatory guidances are pending for host %s: [%s]" + __FUNCTION__ + (List.length host_pending_mandatory_guidances) + (Ref.string_of host) + (host_pending_mandatory_guidances + |> List.map Updateinfo.Guidance.of_pending_guidance + |> List.map Updateinfo.Guidance.to_string + |> String.concat ";" + ) ; + raise + Api_errors.( + Server_error + (host_pending_mandatory_guidances_not_empty, [Ref.string_of host]) + ) + ) + +let assert_host_evacuation_if_required ~__context ~host ~mandatory = + let open Guidance in + let need_evacuation = + List.exists (fun g -> g = RebootHost || g = EvacuateHost) mandatory + in + let resident_vms = + Db.Host.get_resident_VMs ~__context ~self:host + |> List.filter (fun self -> + not (Db.VM.get_is_control_domain ~__context ~self) + ) + in + match (need_evacuation, resident_vms <> []) with + | true, true -> + raise + Api_errors.( + Server_error (host_evacuation_is_required, [Ref.string_of host]) + ) + | _ -> () - | _ :: _ -> - let host' = Ref.string_of host in - raise Api_errors.(Server_error (cannot_restart_device_model, [host'])) diff --git a/ocaml/xapi/updateinfo.ml b/ocaml/xapi/updateinfo.ml index 0640e27be99..092af683232 100644 --- a/ocaml/xapi/updateinfo.ml +++ b/ocaml/xapi/updateinfo.ml @@ -24,8 +24,21 @@ module Guidance = struct | EvacuateHost | RestartDeviceModel | RebootHostOnLivePatchFailure - - type guidance_kind = Absolute | Recommended + | RebootHostOnKernelLivePatchFailure + | RebootHostOnXenLivePatchFailure + | RestartVM + + type kind = Mandatory | Recommended | Full | Livepatch + + let kind_to_string = function + | Recommended -> + "recommended" + | Mandatory -> + "mandatory" + | Full -> + "full" + | Livepatch -> + "livepatch" let compare = Stdlib.compare @@ -40,6 +53,14 @@ module Guidance = struct "RestartDeviceModel" | RebootHostOnLivePatchFailure -> "RebootHostOnLivePatchFailure" + | RebootHostOnKernelLivePatchFailure -> + "RebootHostOnKernelLivePatchFailure" + | RebootHostOnXenLivePatchFailure -> + "RebootHostOnXenLivePatchFailure" + | RestartVM -> + "RestartVM" + + let to_json g = `String (to_string g) let of_string = function | "RebootHost" -> @@ -50,23 +71,45 @@ module Guidance = struct EvacuateHost | "RestartDeviceModel" -> RestartDeviceModel + | "RestartVM" -> + RestartVM | g -> - warn - "Un-recognized guidance in \ - : %s, fallback to \ - RebootHost" - g ; + warn "Un-recognized guidance: %s, fallback to RebootHost" g ; RebootHost - let of_update_guidance = function + let of_pending_guidance = function | `reboot_host -> RebootHost | `reboot_host_on_livepatch_failure -> RebootHostOnLivePatchFailure + | `reboot_host_on_kernel_livepatch_failure -> + RebootHostOnKernelLivePatchFailure + | `reboot_host_on_xen_livepatch_failure -> + RebootHostOnXenLivePatchFailure | `restart_toolstack -> RestartToolstack | `restart_device_model -> RestartDeviceModel + | `restart_vm -> + RestartVM + + let to_pending_guidance = function + | RebootHost -> + Some `reboot_host + | RebootHostOnLivePatchFailure -> + Some `reboot_host_on_livepatch_failure + | RestartToolstack -> + Some `restart_toolstack + | RestartDeviceModel -> + Some `restart_device_model + | RebootHostOnXenLivePatchFailure -> + Some `reboot_host_on_xen_livepatch_failure + | RebootHostOnKernelLivePatchFailure -> + Some `reboot_host_on_kernel_livepatch_failure + | RestartVM -> + Some `restart_vm + | EvacuateHost -> + None end module Applicability = struct @@ -424,28 +467,90 @@ module Severity = struct raise Api_errors.(Server_error (invalid_updateinfo_xml, [])) end +module GuidanceInUpdateInfo = struct + type t = (Guidance.kind * Guidance.t list) list + + let value_of_xml = function + | Xml.Element ("value", _, [Xml.PCData v]) -> + Some (Guidance.of_string v) + | Xml.Element (unexpected, _, _) -> + warn "Ignore unexpected guidance value XML tag %s" unexpected ; + None + | _ -> + None + + exception Unsupported_kind_xml + + let kind_of_xml = function + | "recommended" -> + Guidance.Recommended + | "mandatory" -> + Guidance.Mandatory + | "full" -> + Guidance.Full + | "livepatch" -> + Guidance.Livepatch + | _ -> + raise Unsupported_kind_xml + + let default = + let open Guidance in + [(Mandatory, []); (Recommended, []); (Full, []); (Livepatch, [])] + + let of_xml xml_blocks = + List.fold_left + (fun acc xml_block -> + match xml_block with + | Xml.Element (kind_xml, _, values_in_xml) -> ( + match kind_of_xml kind_xml with + | kind -> + let values = List.filter_map value_of_xml values_in_xml in + (kind, values) :: List.remove_assoc kind acc + | exception Unsupported_kind_xml -> + warn "Unsupported guidance kind XML tag %s" kind_xml ; + acc + ) + | _ -> + warn "Ignore unexpected XML node in guidance." ; + acc + ) + default xml_blocks + + let to_json guidance = + List.map + (fun (kind, guidance_tasks) -> + ( Guidance.kind_to_string kind + , `List (List.map Guidance.to_json guidance_tasks) + ) + ) + guidance + |> fun l -> `Assoc l + + let to_string guidance = to_json guidance |> Yojson.Basic.to_string +end + module UpdateInfo = struct + (** The [guidance] deprecates [rec_guidance], [abs_guidance] and [livepatch_guidance] *) type t = { id: string ; summary: string ; description: string - ; rec_guidance: Guidance.t option - ; abs_guidance: Guidance.t option + ; guidance: GuidanceInUpdateInfo.t ; guidance_applicabilities: Applicability.t list ; spec_info: string ; url: string ; update_type: string - ; livepatch_guidance: Guidance.t option ; livepatches: LivePatch.t list ; issued: Xapi_stdext_date.Date.t ; severity: Severity.t + ; title: string } let guidance_to_string o = Option.value (Option.map Guidance.to_string o) ~default:"" let to_json ui = - let l = + `Assoc [ ("id", `String ui.id) ; ("summary", `String ui.summary) @@ -453,55 +558,31 @@ module UpdateInfo = struct ; ("special-info", `String ui.spec_info) ; ("URL", `String ui.url) ; ("type", `String ui.update_type) - ; ("recommended-guidance", `String (guidance_to_string ui.rec_guidance)) - ; ("absolute-guidance", `String (guidance_to_string ui.abs_guidance)) ; ("issued", `String (Xapi_stdext_date.Date.to_string ui.issued)) ; ("severity", `String (Severity.to_string ui.severity)) + ; ( "livepatches" + , `List (List.map (fun x -> LivePatch.to_json x) ui.livepatches) + ) + ; ("guidance", GuidanceInUpdateInfo.to_json ui.guidance) + ; ("title", `String ui.title) ] - in - match ui.livepatches with - | [] -> - `Assoc l - | _ as lps -> - let l' = - ( "livepatch-guidance" - , `String (guidance_to_string ui.livepatch_guidance) - ) - :: ("livepatches", `List (List.map (fun x -> LivePatch.to_json x) lps)) - :: l - in - `Assoc l' - - let to_string ui = - Printf.sprintf - "id=%s rec_guidance=%s abs_guidance=%s guidance_applicabilities=%s \ - livepatch_guidance=%s livepatches=%s" - ui.id - (guidance_to_string ui.rec_guidance) - (guidance_to_string ui.abs_guidance) - (String.concat ";" - (List.map Applicability.to_string ui.guidance_applicabilities) - ) - (guidance_to_string ui.livepatch_guidance) - (Astring.String.concat ~sep:";" - (List.map LivePatch.to_string ui.livepatches) - ) + + let to_string ui = to_json ui |> Yojson.Basic.to_string let default = { id= "" ; summary= "" ; description= "" - ; rec_guidance= None - ; abs_guidance= None + ; guidance= GuidanceInUpdateInfo.default ; guidance_applicabilities= [] ; spec_info= "" ; url= "" ; update_type= "" - ; livepatch_guidance= None ; livepatches= [] ; issued= Xapi_stdext_date.Date.epoch ; severity= Severity.None + ; title= "" } let assert_valid_updateinfo = function @@ -520,6 +601,9 @@ module UpdateInfo = struct raise Api_errors.(Server_error (invalid_updateinfo_xml, [])) ) + let get_guidances_of_kind ~kind updateinfo = + Option.value (List.assoc_opt kind updateinfo.guidance) ~default:[] + let of_xml = function | Xml.Element ("updates", _, children) -> List.filter_map @@ -547,15 +631,11 @@ module UpdateInfo = struct {acc with summary= v} | Xml.Element ("description", _, [Xml.PCData v]) -> {acc with description= v} - | Xml.Element ("recommended_guidance", _, [Xml.PCData v]) - -> - {acc with rec_guidance= Some (Guidance.of_string v)} - | Xml.Element ("absolute_guidance", _, [Xml.PCData v]) -> - {acc with abs_guidance= Some (Guidance.of_string v)} - | Xml.Element ("livepatch_guidance", _, [Xml.PCData v]) -> + | Xml.Element ("guidance", _, guidance_blocks) -> { acc with - livepatch_guidance= Some (Guidance.of_string v) + guidance= + GuidanceInUpdateInfo.of_xml guidance_blocks } | Xml.Element ("guidance_applicabilities", _, apps) -> { @@ -596,6 +676,8 @@ module UpdateInfo = struct warn "%s" (ExnHelper.string_of_exn e) ; acc ) + | Xml.Element ("title", _, [Xml.PCData v]) -> + {acc with title= v} | _ -> acc ) @@ -627,23 +709,18 @@ end module HostUpdates = struct type t = { host: string - ; rec_guidances: Guidance.t list - ; abs_guidances: Guidance.t list + ; guidance: GuidanceInUpdateInfo.t ; rpms: Rpm.Pkg.t list ; update_ids: string list ; livepatches: LivePatch.t list } let to_json host_updates = - let g_to_j x = `String (Guidance.to_string x) in let p_to_j x = `String (Pkg.to_fullname x) in `Assoc [ ("ref", `String host_updates.host) - ; ( "recommended-guidance" - , `List (List.map g_to_j host_updates.rec_guidances) - ) - ; ("absolute-guidance", `List (List.map g_to_j host_updates.abs_guidances)) + ; ("guidance", GuidanceInUpdateInfo.to_json host_updates.guidance) ; ("RPMS", `List (List.map p_to_j host_updates.rpms)) ; ( "updates" , `List (List.map (fun upd_id -> `String upd_id) host_updates.update_ids) diff --git a/ocaml/xapi/updateinfo.mli b/ocaml/xapi/updateinfo.mli index 69d18571758..7a348db598c 100644 --- a/ocaml/xapi/updateinfo.mli +++ b/ocaml/xapi/updateinfo.mli @@ -20,22 +20,43 @@ module Guidance : sig | EvacuateHost | RestartDeviceModel | RebootHostOnLivePatchFailure + | RebootHostOnKernelLivePatchFailure + | RebootHostOnXenLivePatchFailure + | RestartVM - type guidance_kind = Absolute | Recommended + type kind = Mandatory | Recommended | Full | Livepatch + + val kind_to_string : kind -> string val compare : t -> t -> int val to_string : t -> string + val to_json : t -> Yojson.Basic.t + (* may fail *) val of_string : string -> t - val of_update_guidance : + val of_pending_guidance : [< `reboot_host | `reboot_host_on_livepatch_failure + | `reboot_host_on_kernel_livepatch_failure + | `reboot_host_on_xen_livepatch_failure | `restart_device_model - | `restart_toolstack ] + | `restart_toolstack + | `restart_vm ] -> t + + val to_pending_guidance : + t + -> [> `reboot_host + | `reboot_host_on_livepatch_failure + | `reboot_host_on_kernel_livepatch_failure + | `reboot_host_on_xen_livepatch_failure + | `restart_device_model + | `restart_toolstack + | `restart_vm ] + option end (** The applicability of metadata for one update in updateinfo *) @@ -107,22 +128,34 @@ module Severity : sig val of_string : string -> t end -(** The metadata of one update in updateinfo *) +(** The type of [guidance] in updateinfo metadata. *) +module GuidanceInUpdateInfo : sig + type t = (Guidance.kind * Guidance.t list) list + + val default : t + + val of_xml : Xml.xml list -> t + + val to_json : t -> Yojson.Basic.t + + val to_string : t -> string +end + +(** The metadata of one update in updateinfo. *) module UpdateInfo : sig type t = { id: string ; summary: string ; description: string - ; rec_guidance: Guidance.t option - ; abs_guidance: Guidance.t option + ; guidance: GuidanceInUpdateInfo.t ; guidance_applicabilities: Applicability.t list ; spec_info: string ; url: string ; update_type: string - ; livepatch_guidance: Guidance.t option ; livepatches: LivePatch.t list ; issued: Xapi_stdext_date.Date.t ; severity: Severity.t + ; title: string } val to_json : t -> Yojson.Basic.t @@ -132,13 +165,14 @@ module UpdateInfo : sig val of_xml : Xml.xml -> (string * t) list val of_xml_file : string -> (string * t) list + + val get_guidances_of_kind : kind:Guidance.kind -> t -> Guidance.t list end module HostUpdates : sig type t = { host: string - ; rec_guidances: Guidance.t list - ; abs_guidances: Guidance.t list + ; guidance: GuidanceInUpdateInfo.t ; rpms: Rpm.Pkg.t list ; update_ids: string list ; livepatches: LivePatch.t list diff --git a/ocaml/xapi/xapi_db_upgrade.ml b/ocaml/xapi/xapi_db_upgrade.ml index 2975f885168..05f20f083e7 100644 --- a/ocaml/xapi/xapi_db_upgrade.ml +++ b/ocaml/xapi/xapi_db_upgrade.ml @@ -859,6 +859,51 @@ let empty_pool_uefi_certificates = ) } +(* 1. Replace reboot_host_on_livepatch_failure in host.pending_guidances \ + * with reboot_host_on_kernel_livepatch_failure and \ + * reboot_host_on_xen_livepatch_failure in \ + * host.pending_guidances_recommended. + * 2. Move the rest guidances in \ + * host.pending_guidances into host.pending_guidances_recommended *) +let upgrade_update_guidance = + { + description= + "Upgrade pending update gudiances" + (* TODO: update below schema version to which the feature branch got merged with *) + ; version= + (fun x -> + x + < ( Datamodel_common.nile_release_schema_major_vsn + , Datamodel_common.nile_release_schema_minor_vsn + ) + ) + (* the version where update guidance improvement is made *) + ; fn= + (fun ~__context -> + Db.Host.get_all ~__context + |> List.iter (fun self -> + if + List.mem `reboot_host_on_livepatch_failure + (Db.Host.get_pending_guidances ~__context ~self) + then ( + Db.Host.add_pending_guidances_recommended ~__context ~self + ~value:`reboot_host_on_kernel_livepatch_failure ; + Db.Host.add_pending_guidances_recommended ~__context ~self + ~value:`reboot_host_on_xen_livepatch_failure ; + Db.Host.remove_pending_guidances ~__context ~self + ~value:`reboot_host_on_livepatch_failure + ) ; + List.iter + (fun g -> + Db.Host.add_pending_guidances_recommended ~__context ~self + ~value:g + ) + (Db.Host.get_pending_guidances ~__context ~self) ; + Db.Host.set_pending_guidances ~__context ~self ~value:[] + ) + ) + } + let rules = [ upgrade_domain_type @@ -887,6 +932,7 @@ let rules = ; upgrade_secrets ; remove_legacy_ssl_support ; empty_pool_uefi_certificates + ; upgrade_update_guidance ] (* Maybe upgrade most recent db *) diff --git a/ocaml/xapi/xapi_host.ml b/ocaml/xapi/xapi_host.ml index 2006e4e2710..875b3be5cf2 100644 --- a/ocaml/xapi/xapi_host.ml +++ b/ocaml/xapi/xapi_host.ml @@ -67,19 +67,25 @@ let set_power_on_mode ~__context ~self ~power_on_mode ~power_on_config = Xapi_host_helpers.update_allowed_operations ~__context ~self (** Before we re-enable this host we make sure it's safe to do so. It isn't if: - + we're in the middle of an HA shutdown/reboot and have our fencing temporarily disabled. - + xapi hasn't properly started up yet. - + HA is enabled and this host has broken storage or networking which would cause protected VMs - to become non-agile + + there are pending mandatory guidances on the host + + we're in the middle of an HA shutdown/reboot and have our fencing temporarily disabled. + + xapi hasn't properly started up yet. + + HA is enabled and this host has broken storage or networking which would cause protected VMs + to become non-agile *) let assert_safe_to_reenable ~__context ~self = assert_startup_complete () ; + Repository_helpers.assert_no_host_pending_mandatory_guidance ~__context + ~host:self ; let host_disabled_until_reboot = try bool_of_string (Localdb.get Constants.host_disabled_until_reboot) with _ -> false in if host_disabled_until_reboot then - raise (Api_errors.Server_error (Api_errors.host_disabled_until_reboot, [])) ; + raise + (Api_errors.Server_error + (Api_errors.host_disabled_until_reboot, [Ref.string_of self]) + ) ; if Db.Pool.get_ha_enabled ~__context ~self:(Helpers.get_pool ~__context) then ( let pbds = Db.Host.get_PBDs ~__context ~self in let unplugged_pbds = @@ -1056,7 +1062,8 @@ let create ~__context ~uuid ~name_label ~name_description:_ ~hostname ~address ~control_domain:Ref.null ~updates_requiring_reboot:[] ~iscsi_iqn:"" ~multipathing:false ~uefi_certificates:"" ~editions:[] ~pending_guidances:[] ~tls_verification_enabled ~last_software_update ~recommended_guidances:[] - ~latest_synced_updates_applied:`unknown ; + ~latest_synced_updates_applied:`unknown ~pending_guidances_recommended:[] + ~pending_guidances_full:[] ~last_update_hash:"" ; (* If the host we're creating is us, make sure its set to live *) Db.Host_metrics.set_last_updated ~__context ~self:metrics ~value:(Date.of_float (Unix.gettimeofday ())) ; @@ -3001,7 +3008,7 @@ let apply_updates ~__context ~self ~hash = (* This function runs on master host *) Helpers.assert_we_are_master ~__context ; Pool_features.assert_enabled ~__context ~f:Features.Updates ; - let guidances, warnings = + let warnings = Xapi_pool_helpers.with_pool_operation ~__context ~self:(Helpers.get_pool ~__context) ~doc:"Host.apply_updates" ~op:`apply_updates @@ -3009,6 +3016,10 @@ let apply_updates ~__context ~self ~hash = let pool = Helpers.get_pool ~__context in if Db.Pool.get_ha_enabled ~__context ~self:pool then raise Api_errors.(Server_error (ha_is_enabled, [])) ; + if Db.Host.get_enabled ~__context ~self then ( + disable ~__context ~host:self ; + Xapi_host_helpers.update_allowed_operations ~__context ~self + ) ; Xapi_host_helpers.with_host_operation ~__context ~self ~doc:"Host.apply_updates" ~op:`apply_updates @@ fun () -> Repository.apply_updates ~__context ~host:self ~hash @@ -3016,15 +3027,8 @@ let apply_updates ~__context ~self ~hash = Db.Host.set_last_software_update ~__context ~self ~value:(get_servertime ~__context ~host:self) ; Db.Host.set_latest_synced_updates_applied ~__context ~self ~value:`yes ; - List.map - (fun g -> - [ - Api_errors.updates_require_recommended_guidance - ; Updateinfo.Guidance.to_string g - ] - ) - guidances - @ warnings + Db.Host.set_last_update_hash ~__context ~self ~value:hash ; + warnings let cc_prep () = let cc = "CC_PREPARATIONS" in @@ -3053,3 +3057,14 @@ let set_https_only ~__context ~self ~value = | true -> (* it is illegal changing the firewall/https config in CC/FIPS mode *) raise (Api_errors.Server_error (Api_errors.illegal_in_fips_mode, [])) + +let emergency_clear_mandatory_guidance ~__context = + debug "Host.emergency_clear_mandatory_guidance" ; + let self = Helpers.get_localhost ~__context in + Db.Host.get_pending_guidances ~__context ~self + |> List.iter (fun g -> + let open Updateinfo.Guidance in + let s = g |> of_pending_guidance |> to_string in + info "%s: %s is cleared" __FUNCTION__ s + ) ; + Db.Host.set_pending_guidances ~__context ~self ~value:[] diff --git a/ocaml/xapi/xapi_host.mli b/ocaml/xapi/xapi_host.mli index 952dbed0f7e..39f20223c13 100644 --- a/ocaml/xapi/xapi_host.mli +++ b/ocaml/xapi/xapi_host.mli @@ -557,3 +557,5 @@ val copy_primary_host_certs : __context:Context.t -> host:API.ref_host -> unit val set_https_only : __context:Context.t -> self:API.ref_host -> value:bool -> unit + +val emergency_clear_mandatory_guidance : __context:Context.t -> unit diff --git a/ocaml/xapi/xapi_host_helpers.ml b/ocaml/xapi/xapi_host_helpers.ml index f9b38a84c31..040f5782273 100644 --- a/ocaml/xapi/xapi_host_helpers.ml +++ b/ocaml/xapi/xapi_host_helpers.ml @@ -37,6 +37,7 @@ let all_operations = ; `vm_migrate ; `power_on ; `apply_updates + ; `enable ] (** Returns a table of operations -> API error options (None if the operation would be ok) *) @@ -86,7 +87,7 @@ let valid_operations ~__context record _ref' = if List.mem `apply_updates current_ops then set_errors Api_errors.other_operation_in_progress ["host"; _ref; host_operation_to_string `apply_updates] - [`reboot; `shutdown] ; + [`reboot; `shutdown; `enable] ; (* Prevent more than one provision happening at a time to prevent extreme dom0 load (in the case of the debian template). Once the template becomes a 'real' template we can relax this. *) @@ -344,6 +345,42 @@ let assert_xen_compatible () = if not compatible then raise Api_errors.(Server_error (xen_incompatible, [])) +let remove_pending_guidance ~__context ~self ~value = + let h = Db.Host.get_name_label ~__context ~self in + if + List.exists + (fun g -> g = value) + (Db.Host.get_pending_guidances ~__context ~self) + then ( + debug "Remove guidance [%s] from host [%s]'s pending_guidances list" + Updateinfo.Guidance.(of_pending_guidance value |> to_string) + h ; + Db.Host.remove_pending_guidances ~__context ~self ~value + ) ; + + if + List.exists + (fun g -> g = value) + (Db.Host.get_pending_guidances_recommended ~__context ~self) + then ( + debug + "Remove guidance [%s] from host [%s]'s pending_guidances_recommended list" + Updateinfo.Guidance.(of_pending_guidance value |> to_string) + h ; + Db.Host.remove_pending_guidances_recommended ~__context ~self ~value + ) ; + + if + List.exists + (fun g -> g = value) + (Db.Host.get_pending_guidances_full ~__context ~self) + then ( + debug "Remove guidance [%s] from host [%s]'s pending_guidances_full list" + Updateinfo.Guidance.(of_pending_guidance value |> to_string) + h ; + Db.Host.remove_pending_guidances_full ~__context ~self ~value + ) + let consider_enabling_host_nolock ~__context = debug "Xapi_host_helpers.consider_enabling_host_nolock called" ; (* If HA is enabled only consider marking the host as enabled if all the storage plugs in successfully. @@ -372,20 +409,38 @@ let consider_enabling_host_nolock ~__context = letting a machine with no fencing touch any VMs. Once the host reboots we can safely clear the flag 'host_disabled_until_reboot' *) let pool = Helpers.get_pool ~__context in - Db.Host.remove_pending_guidances ~__context ~self:localhost - ~value:`restart_toolstack ; + let if_no_pending_guidances f = + let host_pending_mandatory_guidances = + Db.Host.get_pending_guidances ~__context ~self:localhost + in + if host_pending_mandatory_guidances <> [] then + debug + "Host.enabled: there are %d pending mandatory guidances on host \ + (%s): [%s]. Leave host disabled." + (List.length host_pending_mandatory_guidances) + (Ref.string_of localhost) + (String.concat ";" + (List.map Updateinfo.Guidance.to_string + (List.map Updateinfo.Guidance.of_pending_guidance + host_pending_mandatory_guidances + ) + ) + ) + else + f () + in if !Xapi_globs.on_system_boot then ( - debug - "Host.enabled: system has just restarted: setting localhost to enabled" ; - Db.Host.set_enabled ~__context ~self:localhost ~value:true ; - Db.Host.remove_pending_guidances ~__context ~self:localhost - ~value:`reboot_host ; - Db.Host.remove_pending_guidances ~__context ~self:localhost - ~value:`reboot_host_on_livepatch_failure ; - update_allowed_operations ~__context ~self:localhost ; - Localdb.put Constants.host_disabled_until_reboot "false" ; - (* Start processing pending VM powercycle events *) - Local_work_queue.start_vm_lifecycle_queue () + debug "Host.enabled: system has just restarted" ; + if_no_pending_guidances (fun () -> + debug + "Host.enabled: system has just restarted and no pending mandatory \ + guidances: setting localhost to enabled" ; + Db.Host.set_enabled ~__context ~self:localhost ~value:true ; + update_allowed_operations ~__context ~self:localhost ; + Localdb.put Constants.host_disabled_until_reboot "false" ; + (* Start processing pending VM powercycle events *) + Local_work_queue.start_vm_lifecycle_queue () + ) ) else if try bool_of_string (Localdb.get Constants.host_disabled_until_reboot) with _ -> false @@ -396,11 +451,17 @@ let consider_enabling_host_nolock ~__context = else ( debug "Host.enabled: system not just rebooted && host_disabled_until_reboot \ - not set: setting localhost to enabled" ; - Db.Host.set_enabled ~__context ~self:localhost ~value:true ; - update_allowed_operations ~__context ~self:localhost ; - (* Start processing pending VM powercycle events *) - Local_work_queue.start_vm_lifecycle_queue () + not set" ; + if_no_pending_guidances (fun () -> + debug + "Host.enabled: system not just rebooted && \ + host_disabled_until_reboot not set and no pending mandatory \ + guidances: setting localhost to enabled" ; + Db.Host.set_enabled ~__context ~self:localhost ~value:true ; + update_allowed_operations ~__context ~self:localhost ; + (* Start processing pending VM powercycle events *) + Local_work_queue.start_vm_lifecycle_queue () + ) ) ; (* If Host has been enabled and HA is also enabled then tell the master to recompute its plan *) if diff --git a/ocaml/xapi/xapi_host_helpers.mli b/ocaml/xapi/xapi_host_helpers.mli index 0434dabdf7a..519aa34a560 100644 --- a/ocaml/xapi/xapi_host_helpers.mli +++ b/ocaml/xapi/xapi_host_helpers.mli @@ -100,6 +100,20 @@ val assert_xen_compatible : unit -> unit currently installed xenctrl library and the currently running Xen hypervisor.) Raises XEN_INCOMPATIBLE if not, and caches the outcome of the check. *) +val remove_pending_guidance : + __context:Context.t + -> self:API.ref_host + -> value: + [ `reboot_host + | `reboot_host_on_kernel_livepatch_failure + | `reboot_host_on_livepatch_failure + | `reboot_host_on_xen_livepatch_failure + | `restart_device_model + | `restart_toolstack + | `restart_vm ] + -> unit +(** Removes update guidance from a host's all pending guidances lists. *) + module Host_requires_reboot : sig val set : unit -> unit (** [set ()] is used to signal the host needs a reboot. This could be, for diff --git a/ocaml/xapi/xapi_pool.ml b/ocaml/xapi/xapi_pool.ml index 2db4baa3bb7..f4736a1a61f 100644 --- a/ocaml/xapi/xapi_pool.ml +++ b/ocaml/xapi/xapi_pool.ml @@ -3359,11 +3359,14 @@ let set_repositories ~__context ~self ~value = (fun x -> if not (List.mem x existings) then ( Db.Repository.set_hash ~__context ~self:x ~value:"" ; - Repository.reset_updates_in_cache () + Repository.reset_updates_in_cache () ; + Db.Pool.set_last_update_sync ~__context ~self ~value:Date.epoch ) ) value ; - Db.Pool.set_repositories ~__context ~self ~value + Db.Pool.set_repositories ~__context ~self ~value ; + if Db.Pool.get_repositories ~__context ~self = [] then + Db.Pool.set_last_update_sync ~__context ~self ~value:Date.epoch let add_repository ~__context ~self ~value = Xapi_pool_helpers.with_pool_operation ~__context ~self @@ -3373,7 +3376,8 @@ let add_repository ~__context ~self ~value = if not (List.mem value existings) then ( Db.Pool.add_repositories ~__context ~self ~value ; Db.Repository.set_hash ~__context ~self:value ~value:"" ; - Repository.reset_updates_in_cache () + Repository.reset_updates_in_cache () ; + Db.Pool.set_last_update_sync ~__context ~self ~value:Date.epoch ) let remove_repository ~__context ~self ~value = @@ -3388,7 +3392,9 @@ let remove_repository ~__context ~self ~value = ) ) (Db.Pool.get_repositories ~__context ~self) ; - Db.Pool.remove_repositories ~__context ~self ~value + Db.Pool.remove_repositories ~__context ~self ~value ; + if Db.Pool.get_repositories ~__context ~self = [] then + Db.Pool.set_last_update_sync ~__context ~self ~value:Date.epoch let sync_updates ~__context ~self ~force ~token ~token_id = Pool_features.assert_enabled ~__context ~f:Features.Updates ; diff --git a/ocaml/xapi/xapi_vm.ml b/ocaml/xapi/xapi_vm.ml index d6c6c35f4b2..e0de06045e1 100644 --- a/ocaml/xapi/xapi_vm.ml +++ b/ocaml/xapi/xapi_vm.ml @@ -673,7 +673,8 @@ let create ~__context ~name_label ~name_description ~power_state ~user_version ~is_vmss_snapshot:false ~appliance ~start_delay ~shutdown_delay ~order ~suspend_SR ~version ~generation_id ~hardware_platform_version ~has_vendor_device ~requires_reboot:false ~reference_label ~domain_type - ~pending_guidances:[] ~recommended_guidances:[] ; + ~pending_guidances:[] ~recommended_guidances:[] + ~pending_guidances_recommended:[] ~pending_guidances_full:[] ; Xapi_vm_lifecycle.update_allowed_operations ~__context ~self:vm_ref ; update_memory_overhead ~__context ~vm:vm_ref ; update_vm_virtual_hardware_platform_version ~__context ~vm:vm_ref ; @@ -1611,6 +1612,19 @@ let set_NVRAM_EFI_variables ~__context ~self ~value = ) let restart_device_models ~__context ~self = + let power_state = Db.VM.get_power_state ~__context ~self in + if power_state <> `Running then + raise + Api_errors.( + Server_error + ( vm_bad_power_state + , [ + Ref.string_of self + ; Record_util.power_state_to_string `Running + ; Record_util.power_state_to_string power_state + ] + ) + ) ; let host = Db.VM.get_resident_on ~__context ~self in (* As it is implemented as a localhost migration, just reuse message * forwarding of "pool_migrate" to handle "allowed operation" and "message diff --git a/ocaml/xapi/xapi_vm_clone.ml b/ocaml/xapi/xapi_vm_clone.ml index 0d0e9be86e9..169d0b3d987 100644 --- a/ocaml/xapi/xapi_vm_clone.ml +++ b/ocaml/xapi/xapi_vm_clone.ml @@ -395,7 +395,8 @@ let copy_vm_record ?snapshot_info_record ~__context ~vm ~disk_op ~new_name ~has_vendor_device:all.Db_actions.vM_has_vendor_device ~requires_reboot:false ~reference_label:all.Db_actions.vM_reference_label ~domain_type:all.Db_actions.vM_domain_type ~nVRAM:all.Db_actions.vM_NVRAM - ~pending_guidances:[] ~recommended_guidances:[] ; + ~pending_guidances:[] ~recommended_guidances:[] + ~pending_guidances_recommended:[] ~pending_guidances_full:[] ; (* update the VM's parent field in case of snapshot. Note this must be done after "ref" has been created, so that its "children" field can be updated by the database layer *) ( match disk_op with diff --git a/ocaml/xapi/xapi_vm_lifecycle.ml b/ocaml/xapi/xapi_vm_lifecycle.ml index 53f6076e89d..d90da39619e 100644 --- a/ocaml/xapi/xapi_vm_lifecycle.ml +++ b/ocaml/xapi/xapi_vm_lifecycle.ml @@ -816,6 +816,42 @@ let checkpoint_in_progress ~__context ~vm = (List.map snd (Db.VM.get_current_operations ~__context ~self:vm)) |> List.mem `checkpoint +let remove_pending_guidance ~__context ~self ~value = + let v = Db.VM.get_name_label ~__context ~self in + if + List.exists + (fun g -> g = value) + (Db.VM.get_pending_guidances ~__context ~self) + then ( + debug "Remove guidance [%s] from vm [%s]'s pending_guidances list" + Updateinfo.Guidance.(of_pending_guidance value |> to_string) + v ; + Db.VM.remove_pending_guidances ~__context ~self ~value + ) ; + + if + List.exists + (fun g -> g = value) + (Db.VM.get_pending_guidances_recommended ~__context ~self) + then ( + debug + "Remove guidance [%s] from vm [%s]'s pending_guidances_recommended list" + Updateinfo.Guidance.(of_pending_guidance value |> to_string) + v ; + Db.VM.remove_pending_guidances_recommended ~__context ~self ~value + ) ; + + if + List.exists + (fun g -> g = value) + (Db.VM.get_pending_guidances_full ~__context ~self) + then ( + debug "Remove guidance [%s] from vm [%s]'s pending_guidances_full list" + Updateinfo.Guidance.(of_pending_guidance value |> to_string) + v ; + Db.VM.remove_pending_guidances_full ~__context ~self ~value + ) + (** 1. Called on new VMs (clones, imports) and on server start to manually refresh the power state, allowed_operations field etc. Current-operations won't be cleaned @@ -823,7 +859,11 @@ let checkpoint_in_progress ~__context ~vm = let force_state_reset_keep_current_operations ~__context ~self ~value:state = (* First update the power_state. Some operations below indirectly rely on this. *) Db.VM.set_power_state ~__context ~self ~value:state ; + if state = `Suspended then + remove_pending_guidance ~__context ~self ~value:`restart_device_model ; if state = `Halted then ( + remove_pending_guidance ~__context ~self ~value:`restart_device_model ; + remove_pending_guidance ~__context ~self ~value:`restart_vm ; (* mark all devices as disconnected *) List.iter (fun vbd -> diff --git a/ocaml/xapi/xapi_vm_migrate.ml b/ocaml/xapi/xapi_vm_migrate.ml index f9878514003..13d112fd3ce 100644 --- a/ocaml/xapi/xapi_vm_migrate.ml +++ b/ocaml/xapi/xapi_vm_migrate.ml @@ -479,6 +479,9 @@ let remove_stale_pcis ~__context ~vm = let pool_migrate_complete ~__context ~vm ~host:_ = let id = Db.VM.get_uuid ~__context ~self:vm in debug "VM.pool_migrate_complete %s" id ; + (* clear RestartDeviceModel guidance on VM migrate *) + Xapi_vm_lifecycle.remove_pending_guidance ~__context ~self:vm + ~value:`restart_device_model ; let dbg = Context.string_of_task __context in let queue_name = Xapi_xenops_queue.queue_of_vm ~__context ~self:vm in if Xapi_xenops.vm_exists_in_xenopsd queue_name dbg id then ( diff --git a/ocaml/xapi/xapi_xenops.ml b/ocaml/xapi/xapi_xenops.ml index bbb2389cc59..ac92853d104 100644 --- a/ocaml/xapi/xapi_xenops.ml +++ b/ocaml/xapi/xapi_xenops.ml @@ -1999,6 +1999,9 @@ let update_vm ~__context id = xenapi_of_xenops_power_state (Option.map (fun x -> (snd x).Vm.power_state) info) in + let power_state_before_update = + Db.VM.get_power_state ~__context ~self + in (* We preserve the current_domain_type of suspended VMs like we preserve the currently_attached fields for VBDs/VIFs etc - it's important to know whether suspended VMs are going to resume into PV or PVinPVH for example. @@ -2275,13 +2278,28 @@ let update_vm ~__context id = try Option.iter (fun (_, state) -> - debug "xenopsd event: Updating VM %s last_start_time <- %s" - id - (Date.to_string (Date.of_float state.Vm.last_start_time)) ; let metrics = Db.VM.get_metrics ~__context ~self in let start_time = Date.of_float state.Vm.last_start_time in - Db.VM_metrics.set_start_time ~__context ~self:metrics - ~value:start_time ; + if + start_time + <> Db.VM_metrics.get_start_time ~__context ~self:metrics + then ( + debug + "xenopsd event: Updating VM %s last_start_time <- %s" id + (Date.to_string (Date.of_float state.Vm.last_start_time)) ; + Db.VM_metrics.set_start_time ~__context ~self:metrics + ~value:start_time ; + if + (* VM start and VM reboot *) + power_state = `Running + && power_state_before_update <> `Suspended + then ( + Xapi_vm_lifecycle.remove_pending_guidance ~__context + ~self ~value:`restart_device_model ; + Xapi_vm_lifecycle.remove_pending_guidance ~__context + ~self ~value:`restart_vm + ) + ) ; create_guest_metrics_if_needed () ; let gm = Db.VM.get_guest_metrics ~__context ~self in let update_time = @@ -3507,10 +3525,7 @@ let set_resident_on ~__context ~self = refresh_vm ~__context ~self ; !trigger_xenapi_reregister () ; (* Any future XenAPI updates will trigger events, but we might have missed one so: *) - Xenopsd_metadata.update ~__context ~self ; - Db.VM.remove_pending_guidances ~__context ~self ~value:`restart_device_model ; - Db.VM.remove_recommended_guidances ~__context ~self - ~value:`restart_device_model + Xenopsd_metadata.update ~__context ~self let update_debug_info __context t = let task = Context.get_task_id __context in diff --git a/ocaml/xe-cli/newcli.ml b/ocaml/xe-cli/newcli.ml index d197b849a94..7c07c199512 100644 --- a/ocaml/xe-cli/newcli.ml +++ b/ocaml/xe-cli/newcli.ml @@ -435,6 +435,38 @@ let assert_filename_permitted ?(permit_cwd = false) permitted_filenames filename | _ -> () +let do_http_get ofd url exit_code f = + try + let rec doit url = + let server, path = parse_url url in + debug "Opening connection to server '%s' path '%s'\n%!" server path ; + with_open_tcp server @@ fun (ic, oc) -> + Printf.fprintf oc "GET %s HTTP/1.0\r\n\r\n" path ; + flush oc ; + (* Get the result header immediately *) + let resultline = input_line ic in + debug "Got %s\n%!" resultline ; + match http_response_code resultline with + | 200 -> + f ic ; marshal ofd (Response OK) + | 302 -> + let headers = read_rest_of_headers ic in + let newloc = List.assoc "location" headers in + (* see above about Unixfd.with_connection *) + close_in_noerr ic ; close_out_noerr oc ; doit newloc + | _ -> + failwith "Unhandled response code" + in + doit url + with + | ClientSideError msg -> + marshal ofd (Response Failed) ; + Printf.fprintf stderr "Operation failed. Error: %s\n" msg ; + exit_code := Some 1 + | e -> + debug "HTTP GET failure: %s\n%!" (Printexc.to_string e) ; + marshal ofd (Response Failed) + let main_loop ifd ofd permitted_filenames = (* Intially exchange version information *) let major', minor' = @@ -709,64 +741,55 @@ let main_loop ifd ofd permitted_filenames = the normal communication channel *) marshal ofd (Response Failed) ) - | Command (HttpGet (filename, url)) -> ( - try - let rec doit url = - let server, path = parse_url url in - debug "Opening connection to server '%s' path '%s'\n%!" server path ; - with_open_tcp server @@ fun (ic, oc) -> - Printf.fprintf oc "GET %s HTTP/1.0\r\n\r\n" path ; - flush oc ; - (* Get the result header immediately *) - let resultline = input_line ic in - debug "Got %s\n%!" resultline ; - match http_response_code resultline with - | 200 -> - let file_ch = - if filename = "" then - Unix.out_channel_of_descr (Unix.dup Unix.stdout) - else ( - assert_filename_permitted ~permit_cwd:true permitted_filenames - filename ; - try - open_out_gen - [Open_wronly; Open_creat; Open_excl] - 0o600 filename - with e -> raise (ClientSideError (Printexc.to_string e)) - ) - in - while input_line ic <> "\r" do - () - done ; - Pervasiveext.finally - (fun () -> - copy_with_heartbeat ic file_ch heartbeat_fun ; - marshal ofd (Response OK) - ) - (fun () -> try close_out file_ch with _ -> ()) - | 302 -> - let headers = read_rest_of_headers ic in - let newloc = List.assoc "location" headers in - (* see above about Unixfd.with_connection *) - close_in_noerr ic ; close_out_noerr oc ; doit newloc - | _ -> - failwith "Unhandled response code" - in - doit url - with - | ClientSideError msg -> - marshal ofd (Response Failed) ; - Printf.fprintf stderr "Operation failed. Error: %s\n" msg ; - exit_code := Some 1 - | e -> ( - match e with - | Filename_not_permitted _ -> - raise e - | _ -> - debug "HttpGet failure: %s\n%!" (Printexc.to_string e) ; - marshal ofd (Response Failed) - ) - ) + | Command (HttpGet (filename, url)) -> + do_http_get ofd url exit_code (fun ic -> + let file_ch = + if filename = "" then + Unix.out_channel_of_descr (Unix.dup Unix.stdout) + else ( + assert_filename_permitted ~permit_cwd:true permitted_filenames + filename ; + try + open_out_gen + [Open_wronly; Open_creat; Open_excl] + 0o600 filename + with e -> raise (ClientSideError (Printexc.to_string e)) + ) + in + while input_line ic <> "\r" do + () + done ; + Pervasiveext.finally + (fun () -> copy_with_heartbeat ic file_ch heartbeat_fun) + (fun () -> try close_out file_ch with _ -> ()) + ) + | Command (PrintHttpGetJson url) -> + do_http_get ofd url exit_code (fun ic -> + while input_line ic <> "\r" do + () + done ; + Yojson.Basic.from_channel ic + |> Yojson.Basic.pretty_to_string + |> print_endline ; + flush stdout + ) + | Command (PrintUpdateGuidance url) -> + do_http_get ofd url exit_code (fun ic -> + while input_line ic <> "\r" do + () + done ; + Yojson.Basic.from_channel ic |> Yojson.Basic.Util.member "hosts" + |> function + | `List [] -> + raise (ClientSideError "No host data returned") + | `List (host :: _) -> + Yojson.Basic.Util.member "guidance" host + |> Yojson.Basic.pretty_to_string + |> print_endline ; + flush stdout + | _ -> + raise (ClientSideError "Unknown data format") + ) | Command Prompt -> let data = input_line stdin in marshal ofd (Blob (Chunk (Int32.of_int (String.length data)))) ; diff --git a/quality-gate.sh b/quality-gate.sh index 224e852aa32..d33edacff02 100755 --- a/quality-gate.sh +++ b/quality-gate.sh @@ -3,7 +3,7 @@ set -e list-hd () { - N=318 + N=316 LIST_HD=$(git grep -r --count 'List.hd' -- **/*.ml | cut -d ':' -f 2 | paste -sd+ - | bc) if [ "$LIST_HD" -eq "$N" ]; then echo "OK counted $LIST_HD List.hd usages"