Make xe warn about ignored command-line parameters - #7255
Conversation
0d92f07 to
1f68280
Compare
| (* Return the list of k=v pairs for maps. | ||
| Works for key which is not follow by a ':', | ||
| also match old syntax 'device-config-key' for backwards compatability *) | ||
| (* The [(key, value)] contents of a map-valued parameter [name:key=value], as a |
There was a problem hiding this comment.
It looks like you forgot to remove this comment as well. It is not related to "get_chunks" if I'm not mistaken.
psafont
left a comment
There was a problem hiding this comment.
This is excellent work, the cli code has accumulated a lot of technical debt and is it need of care, this provides quite a bit of it.
While most of my comments are small nits to reduce duplication, I'm worried about the misuse of Cli_args.to_pairs without an entry-marking function. This makes a leaky interface that makes mistakes easy to do, and so I think to_pairs should be removed from the interface.
| @@ -134,18 +134,15 @@ let waiter printer rpc session_id params task = | |||
| Works for key which is not follow by a ':', | |||
| also match old syntax 'device-config-key' for backwards compatability *) | |||
| let read_map_params name params = | |||
| (* [name:key=value] pairs (also the legacy [name-key=value] form), with the | |||
| [name:] / [name-] prefix stripped. The contents of a map-valued parameter | |||
| are plain data, not tracked CLI arguments. *) | |||
| let len = String.length name + 1 in | |||
There was a problem hiding this comment.
It might be worth ensuring that the dropped character is : or -.
| let pVS_uuid = try List.assoc "pvs-uuid" params with Not_found -> "" in | ||
| let name_label = Cli_args.get "name-label" params in | ||
| let name_description = Cli_args.get_default "name-description" params "" in | ||
| let pVS_uuid = try Cli_args.get "pvs-uuid" params with Not_found -> "" in |
There was a problem hiding this comment.
| let pVS_uuid = try Cli_args.get "pvs-uuid" params with Not_found -> "" in | |
| let pVS_uuid = try Cli_args.get_default "pvs-uuid" params "" in |
| let filter_out pred t = | ||
| {t with pairs= List.filter (fun (k, _) -> not (pred k)) t.pairs} |
There was a problem hiding this comment.
| let filter_out pred t = | |
| {t with pairs= List.filter (fun (k, _) -> not (pred k)) t.pairs} | |
| let filter_out pred t = filter (Fun.negate pred) t |
| let get key t = | ||
| match List.find_opt (fun (k, _) -> k = key) (visible t) with | ||
| | Some (_, e) -> | ||
| mark e ; e.value | ||
| | None -> | ||
| raise Not_found | ||
|
|
||
| let get_opt key t = List.assoc_opt key (visible t) | ||
| let get_opt key t = | ||
| match List.find_opt (fun (k, _) -> k = key) (visible t) with | ||
| | Some (_, e) -> | ||
| mark e ; Some e.value | ||
| | None -> | ||
| None | ||
|
|
There was a problem hiding this comment.
| let get key t = | |
| match List.find_opt (fun (k, _) -> k = key) (visible t) with | |
| | Some (_, e) -> | |
| mark e ; e.value | |
| | None -> | |
| raise Not_found | |
| let get_opt key t = List.assoc_opt key (visible t) | |
| let get_opt key t = | |
| match List.find_opt (fun (k, _) -> k = key) (visible t) with | |
| | Some (_, e) -> | |
| mark e ; Some e.value | |
| | None -> | |
| None | |
| let get_opt key t = | |
| match List.find_opt (fun (k, _) -> k = key) (visible t) with | |
| | Some (_, e) -> | |
| mark e ; Some e.value | |
| | None -> | |
| None | |
| let get k t = match get_opt k t with Some v -> v | None -> raise Not_found |
(I'm not sure whether the formatter will like the one-liner)
| let exists key t = | ||
| match List.find_opt (fun (k, _) -> k = key) (visible t) with | ||
| | Some (_, e) -> | ||
| mark e ; true | ||
| | None -> | ||
| false |
There was a problem hiding this comment.
| let exists key t = | |
| match List.find_opt (fun (k, _) -> k = key) (visible t) with | |
| | Some (_, e) -> | |
| mark e ; true | |
| | None -> | |
| false | |
| let exists key t = get_opt key t |> Option.is_some |
| (* Filter all the records *) | ||
| List.fold_left filter_records_on_fields all_recs | ||
| (Cli_args.to_pairs filter_params) | ||
| (Cli_args.to_pairs (Cli_args.consume filter_params)) |
There was a problem hiding this comment.
Can this use of to_pairs be replaced by map_contents as well?
| |> Cli_args.consume | ||
| |> Cli_args.to_pairs |
| |> Cli_args.consume | ||
| |> Cli_args.to_pairs |
| |> Cli_args.consume | ||
| |> Cli_args.to_pairs |
| (* What xe does with command-line parameters that the command never read. | ||
| Set from xapi.conf (see xapi_globs), read by the CLI server; lives here | ||
| because xapi-cli-server doesn't link xapi-globs. *) | ||
| type cli_report_ignored_parameters = Off | Warn |
There was a problem hiding this comment.
The help messages (cmd_help in cli_frontend and usage in newcli) should also be changed to mention the value off
|
Many thanks for the general feedback. I will do my best to address the
individual comments promptly and efficiently.
One qustion before I deep-dive (likely from tomorrow onward): I do share
the concern about `to_pairs` but have not been able to find a way to not
have it. The two places where I was not sure how to do without it were
(1) logging and (2) passing the parameters to the commands themselves.
Do you have ideas how to tackle these tw?
|
1f68280 to
0680fe7
Compare
I would prefer having two specialized functions that at least have a particular name to warn against using it, unlike the general and seemlesly harmless |
0680fe7 to
44736a1
Compare
|
Guillaume (2026/09/07 06:11 -0700):
@gthvn1 commented on this pull request.
__________________________________________________________________
In [1]ocaml/xapi-cli-server/cli_operations.ml:
> @@ -130,16 +130,9 @@ let waiter printer rpc session_id params task =
)
(fun () -> Client.Task.destroy ~rpc ~session_id ~self:task)
-(* Return the list of k=v pairs for maps.
- Works for key which is not follow by a ':',
- also match old syntax 'device-config-key' for backwards compatability *)
(* The [(key, value)] contents of a map-valued parameter [name:key=value], as a
It looks like you forgot to remove this comment as well. It is not
related to "get_chunks" if I'm not mistaken.
You are right, it should have been dropped as the one above. Now done,
thanks!
|
1e006e5 to
3c42451
Compare
|
Pau Ruiz Safont (2026/09/07 07:16 -0700):
In ocaml/xapi-cli-server/cli_operations.ml:
> @@ -134,18 +134,15 @@ let waiter printer rpc session_id params task =
Works for key which is not follow by a ':',
also match old syntax 'device-config-key' for backwards compatability *)
let read_map_params name params =
+ (* [name:key=value] pairs (also the legacy [name-key=value] form), with the
+ [name:] / [name-] prefix stripped. The contents of a map-valued parameter
+ are plain data, not tracked CLI arguments. *)
let len = String.length name + 1 in
It might be worth ensuring that the dropped character is : or -.
A very good idea, thanks.
As I understand it this is not really a regression, as the code before
this PR was not enforcing : or - as separators either, but I think it is very
sensible to improve this here.
As this is a change in behaviour, it has been implemented in its own
commit. Also, the commit in question appears after all the refactorings
once the code has its final shape.
A prefixed key like other-configuration:foo=bar, which would previously
have been parsed as "other-config" (name of the view), skipped the u, and
then parameter "ration:foo" with value "bar" is no longer understood as
being part of the "other-config" view and will thus not be passed down
to the lower layers.
So, when reporting of ignored parameters is enabled, such a key is now
properly reported as ignored, which would not have been the case without
this suggestion.
In ocaml/xapi-cli-server/cli_operations.ml:
> let ref = Client.PCI.get_by_uuid ~rpc ~session_id ~uuid in
let result = Client.PCI.get_dom0_access_status ~rpc ~session_id ~self:ref in
printer (Cli_printer.PMsg (Record_util.pci_dom0_access_to_string result))
module PVS_site = struct
let introduce printer rpc session_id params =
- let name_label = List.assoc "name-label" params in
- let name_description =
- try List.assoc "name-description" params with Not_found -> ""
- in
- let pVS_uuid = try List.assoc "pvs-uuid" params with Not_found -> "" in
+ let name_label = Cli_args.get "name-label" params in
+ let name_description = Cli_args.get_default "name-description" params "" in
+ let pVS_uuid = try Cli_args.get "pvs-uuid" params with Not_found -> "" in
⬇️ Suggested change
- let pVS_uuid = try Cli_args.get "pvs-uuid" params with Not_found -> "" in
+ let pVS_uuid = try Cli_args.get_default "pvs-uuid" params "" in
Fixed, thanks
In ocaml/xapi-cli-server/cli_args.ml:
> +let filter_out pred t =
+ {t with pairs= List.filter (fun (k, _) -> not (pred k)) t.pairs}
⬇️ Suggested change
-let filter_out pred t =
- {t with pairs= List.filter (fun (k, _) -> not (pred k)) t.pairs}
+let filter_out pred t = filter (Fun.negate pred) t
Applied, thanks
In ocaml/xapi-cli-server/cli_args.ml:
> +let get key t =
+ match List.find_opt (fun (k, _) -> k = key) (visible t) with
+ | Some (_, e) ->
+ mark e ; e.value
+ | None ->
+ raise Not_found
-let get_opt key t = List.assoc_opt key (visible t)
+let get_opt key t =
+ match List.find_opt (fun (k, _) -> k = key) (visible t) with
+ | Some (_, e) ->
+ mark e ; Some e.value
+ | None ->
+ None
⬇️ Suggested change
-let get key t =
- match List.find_opt (fun (k, _) -> k = key) (visible t) with
- | Some (_, e) ->
- mark e ; e.value
- | None ->
- raise Not_found
-
-let get_opt key t = List.assoc_opt key (visible t)
-let get_opt key t =
- match List.find_opt (fun (k, _) -> k = key) (visible t) with
- | Some (_, e) ->
- mark e ; Some e.value
- | None ->
- None
-
+let get_opt key t =
+ match List.find_opt (fun (k, _) -> k = key) (visible t) with
+ | Some (_, e) ->
+ mark e ; Some e.value
+ | None ->
+ None
+
+let get k t = match get_opt k t with Some v -> v | None -> raise Not_found
(I'm not sure whether the formatter will like the one-liner)
Applied
In ocaml/xapi-cli-server/cli_args.ml:
> +let exists key t =
+ match List.find_opt (fun (k, _) -> k = key) (visible t) with
+ | Some (_, e) ->
+ mark e ; true
+ | None ->
+ false
⬇️ Suggested change
-let exists key t =
- match List.find_opt (fun (k, _) -> k = key) (visible t) with
- | Some (_, e) ->
- mark e ; true
- | None ->
- false
+let exists key t = get_opt key t |> Option.is_some
Also applied
In ocaml/xapi-cli-server/cli_args.ml:
> +(* Mark the entry for [key], if any, as read -- without reading its value. *)
+let mark_used key t =
+ match List.find_opt (fun (k, _) -> k = key) (visible t) with
+ | Some (_, e) ->
+ mark e
+ | None ->
+ ()
⬇️ Suggested change
-(* Mark the entry for [key], if any, as read -- without reading its value. *)
-let mark_used key t =
- match List.find_opt (fun (k, _) -> k = key) (visible t) with
- | Some (_, e) ->
- mark e
- | None ->
- ()
+let mark_used key t = (get_opt key t : 'a entry option) |> ignore
Applied
In ocaml/xapi-cli-server/cli_operations.ml:
>
-(* This goes through the list of parameters, extracting any of the form map-nam
e-key=value *)
-(* where map-name is the name of a map in the class. These will be used to set
the key-value *)
-(* pair in the map. Returns a list of params that didn't fit this form *)
+(* The [(key, value)] contents of a map-valued parameter, marked as read, for
+ forwarding wholesale to the API. *)
+let map_contents view = view |> Cli_args.consume |> Cli_args.to_pairs
I think it's worth moving map_contents up in the file so params_except
also uses this function to reduce the amount of location where
Cli_args.to_pairs is used, since it needs to be coupled with some form
of marking.
Good idea, thanks. Applied.
It might be even be worth moving this function to Cli_args as well as
assoc_default_ci to remove to_pairs from the interface, since it's
dangerous due to its need to be paired with a form of
entry-marking.
Done the move, but kept to_pairs at this stage as its only current use
is for logging. NOt to say that getting rid of it is a bad idea, but
rather to say it's not used a lot any more and, ifwe wanted to remove
it, it would probably also be nice to remove the keys function. Perhaps
these removals or renamings or interface changes could be done as a
follow-up?
I see that to_pairs needs to be tested, can the property be tested on
map_contents and assoc_default_ci instead?
Replaced test_from_to_pairs by a map_contents test an dadded a test for
assoc_default_ci.
In ocaml/xapi-cli-server/cli_operations.ml:
> @@ -3279,7 +3292,7 @@ let select_vms ?(include_control_vms = false) ?(include_
template_vms = false)
in
(* Filter all the records *)
List.fold_left filter_records_on_fields all_recs
- (Cli_args.to_pairs filter_params)
+ (Cli_args.to_pairs (Cli_args.consume filter_params))
Can this use of to_pairs be replaced by map_contents as well?
Yes
In ocaml/xapi-cli-server/cli_operations.ml:
> + |> Cli_args.consume
+ |> Cli_args.to_pairs
This is another map_contents.
Fixed
In ocaml/xapi-cli-server/cli_operations.ml:
> + |> Cli_args.consume
+ |> Cli_args.to_pairs
This is another map_contents.
Fixed, too
In [11]ocaml/xapi-cli-server/cli_operations.ml:
> + |> Cli_args.consume
+ |> Cli_args.to_pairs
This is another map_contents.
Also fixed
In [12]ocaml/xapi-consts/constants.ml:
> @@ -317,6 +317,13 @@ let owner_key = "owner"
(* xapi-cli-server doesn't link xapi-globs *)
let use_event_next = ref false
+(* What xe does with command-line parameters that the command never read.
+ Set from xapi.conf (see xapi_globs), read by the CLI server; lives here
+ because xapi-cli-server doesn't link xapi-globs. *)
+type cli_report_ignored_parameters = Off | Warn
The help messages (cmd_help in cli_frontend and usage in newcli) should
also be changed to mention the value off
Indeed! Done.
|
7b549fe to
aa21e09
Compare
Done — |
|
Pau Ruiz Safont (2026/09/09 02:53 -0700):
@psafont approved this pull request.
Thank you so much!
Thank you for your time and insightful comments!
|
| but before the terminating Exit so the client is still listening. On | ||
| success the Exit is sent afterwards; on failure it is the error path's | ||
| job. *) | ||
| Xapi_stdext_pervasives.Pervasiveext.finally run (fun () -> |
There was a problem hiding this comment.
A command that aborts part-way has not read the parameters below its abort point, so they are still unmarked and get reported as "Ignored".
An example:
# xe vdi-create sr-uuid=00000000 name-label=disk1 virtual-size=8GiB type=user sm-config:foo=bar report-ignored-params=warn
Ignored parameter: type
Ignored parameter: sm-config:foo
The uuid you supplied was invalid.
type: SR
uuid: 00000000
There was a problem hiding this comment.
Well spotted, many thanks for having reported this -- I should have
documented it explicitly for the sake of clarity.
My understanding is that the current behaviour is correct, in the sense
that it just tells a user that a parameter that was given has actually
not been consumed, for whatever reason. Do you request a change either
in the code or in the documentation?
aa21e09 to
c27357b
Compare
To better keep track of what the module exports and how this is impacted by the changes to come. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
This function is called only once in the codebase, with a list which is non-empty by construction. As the first argument of the list is special (the name of the program), it is simpler not to add it to the list at all and to pass it along separately. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
It is used only to define cmd_help, whihc is better defined directly. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
Preparatory commit for the upcoming Cli_args migration: express the force/live/copy option handling with plain List.assoc_opt so the later substitution to Cli_args accessors is 1:1 and mechanical. No behaviour change: same validation (bool_of_string still names the offending key on error), same canonicalisation to "true"/"false", same ordering (compress first, then force, live, copy). Removes the local comp2 and the Listext.map_assoc_with_key / restrict_with_default calls. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
Five commands filter the command line down to "everything the framework did not already handle" with an open-coded List.filter (fun (p, _) -> not (List.mem p (extra :: ... :: stdparams))) params Collapse those into a single named helper. No behaviour change. This is also the one place the later ignored-parameter tracking will need to mark the remaining pairs as consumed. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
Three commands prepend a synthetized key=value pair onto the command line to override whatever value the user passed. Name that idiom so the upcoming Cli_args migration has a single call shape to convert rather than three open-coded list conses. No behaviour change. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
pif_reconfigure_ip and pif_reconfigure_ipv6 each defined an identical local read_optional_case_insensitive. Lift it to a module-level assoc_default_ci. No behaviour change. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
Introduce Cli_args: a small abstraction over the association list of key=value parameters parsed from an "xe" command line. Every parameter access in the CLI server now goes through it (get / get_opt / get_default / get_all / exists / add / remove / filter / filter_out / keys), the type is abstract, and the command dispatch type (op in cli_cmdtable) carries string Cli_args.t rather than a raw (string * string) list. This is purely structural: no observable behaviour change. It is the seam through which a later commit will track which parameters a command actually consumed and report the ones that were silently ignored. Notable points: - read_map_params keeps returning a plain (string * string) list, as on master: the contents of a map- or set-valued parameter are data, not tracked CLI arguments, so they are read out of the Cli_args.t rather than kept wrapped in it. - select_vms / select_hosts / select_srs keep List.remove_assoc's first-occurrence semantics via Cli_args.remove. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
Add Cli_args.view: [view prefix t] restricts a Cli_args.t to the entries whose key is [prefix] followed by a separator (the prefix:key=value map syntax, and the legacy prefix-key=value form), presenting those keys with the prefix stripped. It is a lens: the entries are shared with the structure the view is taken from, and the prefix:key entries stay in that structure. read_map_params / read_set_params are now one-liners over view, so the contents of a map- or set-valued parameter reach the Cli_args accessors rather than being extracted into a bare list. Commands that read individual keys of a map (with_specified_database, with_database_vdi) now use Cli_args.view + get/exists directly. No behaviour change: view reproduces read_map_params' previous filter-and-strip exactly. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
Add Cli_maps: one named view constructor per map- or set-valued parameter the CLI understands ([let other_config params = Cli_args.view "other-config" params], etc.). It is the single place, in code, that lists those parameter names, and it removes the repetition of the name strings across cli_operations.ml. read_map_params / read_set_params are dropped; their ~35 call sites now read [Cli_args.to_pairs (Cli_maps.<name> params)] (or Cli_args.keys for the one set-valued parameter, tags). No behaviour change. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
choose_params and select_fields hard-coded the parameter name "params". vm_disk_list_aux worked around that by rebuilding the whole parameter list with vbd-params / vdi-params renamed to params. Add an optional ~key argument instead and pass ~key:"vbd-params" / ~key:"vdi-params" from vm_disk_list_aux; the rebuilds go away. No behaviour change. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
Give each Cli_args entry a mutable [used] flag. Every value accessor
(get / get_opt / get_default / get_all / exists / assoc_default_ci) marks
the entries it touches. Entry records are shared through views, so
marking via a view is visible from the root. Cli_args.unused -- the keys
of entries nobody read -- is defined here but not called yet; nothing
reads [used], so there is no behaviour change. The report of ignored
parameters is a later commit.
Once reads are tracked, a bulk reader that returns the parameters as a
plain list without marking them is a trap: the command does consume those
parameters, yet the report still lists them as ignored. So the same
commit closes that hole. The interface now offers only marking ways to
take a parameter's contents out:
- Cli_args.consume marks every visible entry; a set parameter is read
as consume |> keys.
- Cli_args.map_contents (consume then to_pairs) is the reader for a
map/set parameter and for the "rest of the command line": the
Cli_maps.* reads, params_except, the field filters of
select_vms / *-list, and the option subsets forwarded by
diagnostic_net_stats / host_crashdump_upload / host_bugreport_upload
all go through it. assoc_default_ci, a case-insensitive get_default,
moves into Cli_args alongside it.
- Cli_args.log_args renders the "key=value ..." line for the command
log, censoring the values it is told to; being diagnostic output it
deliberately does not mark.
to_pairs, the unchecked reader, is no longer exposed by cli_args.mli.
override_param marks its synthesized entry (never a user parameter); the
CLI framework marks Cli_args.reserved in exec_command. vm_migrate renamed
host-uuid / host-name to host by round-tripping through to_pairs /
from_pairs, which rebuilt fresh entries and lost the tracking; it now
uses Cli_args.rename_key, which mutates the shared entry records in
place.
Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
Add test_cli_args.ml to the existing suite_alcotest: it exercises Cli_args directly -- which accessors mark a parameter read, that marking through a view or a derived structure reaches the original, that view strips the prefix (and accepts the legacy dash separator), consume / mark_used / rename_key, and that unused reports exactly the untouched keys. This covers the engine that produces the "ignored parameters" report, not the xe command line end to end (which has no test harness in the tree). Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
A scoped Cli_args.view matched an entry by checking only that its key started with the view's prefix, then skipped one character as the separator without looking at it. So view "other-config" also matched "other-configuration", reading its bare key as "ration". No command defines both a map view "foo" and a scalar parameter of the shape "foo<x>bar" that this could divert, so nothing is mis-read today. But it is fragile, and it costs the ignored-parameter report its accuracy: a diverted key would be marked used along with the view and so never reported as ignored. Require the character after the prefix to be ':' or '-' -- the two separators the map/set syntax uses, and what the view docstring already says. This is the only behaviour change in the series; it removes just the matches that were wrong. The preceding commits keep introducing Cli_args.view and Cli_maps with no behaviour change. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
When an invocation carries report-ignored-params=warn -- passed on the command line, via XE_EXTRA_ARGS, or in ~/.xe -- print one "Ignored parameter: <key>" line on stderr for every parameter the command never read. Cli_args.unused feeds a report_ignored_params helper, called from a finally around the command so the report comes out whether the command succeeds or fails. This surfaces the class of bug where an API-side option is named on the command line, not wired into the CLI, and silently dropped. Off unless asked for: a successful xe command is expected to write nothing to stderr, and some scripts rely on that. stderr, not stdout, so data output is untouched. The forwarding (slave) path leaves the report to the master, which holds the real read state. Nothing is reported when the command line fails to parse, the command is unknown, or a required parameter is missing: those are rejected before the command runs. report-ignored-params is a framework parameter, registered in two lists: Cli_args.reserved, so "xe help" does not mistake it for a command name and exec_command marks it consumed (it never reports itself); and stdparams, which the list / select / event-wait commands strip before treating the rest of the command line as filter fields. Without the latter, a global report-ignored-params in ~/.xe would make vm-list & co. fail with "Unknown field". force and multiple are left to the per-command reads; trace and progress keep their exec_command marking. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
…port The previous commit enables the report per invocation (report-ignored-params=warn). Add a second source for the same key: xapi.conf, so an operator can turn the report on for every xe on a host or pool at once, without touching each user's environment. The command-line value wins; xapi.conf is only consulted when the key is absent from the invocation. The mode is stored in a Constants ref because xapi-cli-server does not link xapi-globs; xapi_globs wires the config entry to it, exactly as it already does for use-event-next. This commit is self-contained: reverting it leaves the per-invocation control from the previous commit working. Signed-off-by: Seb Hinderer <sebastien.hinderer@vates.tech>
c27357b to
0b2048c
Compare
Before this PR
As reported in this forum post,
a command like
creates the SR, but neither acts on
other-config:auto-scannor reports that itwent ignored.
After this PR
This PR changes no behaviour until the report is explicitly switched on, by
setting
report-ignored-paramstowarnin one of two ways:report-ignored-params = warnin/etc/xapi.conf.report-ignored-params=warnon thexeinvocation, or oncein
XE_EXTRA_ARGS/~/.xe.A client-side value overrides the server-side one.
With reporting enabled, the command from the previous section writes one more
line:
The ignored parameters are reported on stderr, as diagnostic output usually is.
Because a successful command can have ignored parameters too, this breaks an
xeinvariant — that a successful command writes nothing to stderr, which somescripts rely on. That is why reporting is off by default.
The command's own output, on stdout, is unaffected.
*-listcommands are also unchanged: they read every extrakey=valueas arecord filter (
xe vm-list power-state=running), so an unrecognised one isalready a hard error, not an ignored parameter.
How it works
The report is built by tracking which parameters an
xecommand effectivelyreads as it runs.
This is achieved by making the representation of the command line abstract. It
used to be a plain
(string * string) list, read directly withList.assoc;it is now
Cli_args.t, whose entries are reachable only through theCli_argsAPI. Each accessor —
get,get_opt,exists, and so on — sets ausedflagon the entry it touched. After the command runs,
Cli_args.unusedlists theparameters that were ignored.
Design choices
Complete over early. The report lists exactly what the command left unread,
so it cannot exist until the command has finished. A check before execution
(against the declared parameter lists in
cli_frontend.ml) would catch somemistakes ahead of any side effect, but only some — it cannot tell that
message-createreads the first ofvm-uuid,host-uuid,sr-uuid,pool-uuidpresent, sovm-uuid=X sr-uuid=Ysilently dropssr-uuid. Theafter-the-fact report is the price of catching every case.
Errors caught before the command runs — a command line that does not parse, an
unknown command, a missing required parameter — thus produce no report.
A mode rather than a switch.
report-ignored-paramstakesofforwarnfor now. A stricter
error— exit non-zero when a parameter was ignored eventhough the command itself succeeded — could be added later as a third value,
without a new setting or a rename, and without disturbing the
off/warnalready written on command lines and in
xapi.conf.Injected parameters count as used. Some commands add filter parameters the
user never typed — VM selection, for instance, adds
is-control-domain=falsesocontrol domains are not matched. Those injected parameters are marked as used. A
parameter the user passed for the same key is not, so in a command that forces
it,
is-control-domain=trueis still reported as ignored.progressis kept out of the report;forceandmultipleare not.Framework keys —
server,username,traceand the rest — are marked asused: the command does not read them, but the framework does, so they are not
ignored.
progressis a real command parameter, and strict accuracy wouldreport it when a command does not honour it. But it only controls display — a
progressthe command does not honour costs nothing more than a missingprogress bar — so it stays out of the report.
forceandmultiplechange whatthe command does, and a
forceormultiplethe command ignores can mean theuser misunderstood it, so both stay in.
How to review
Best reviewed commit by commit.
1–3 —
Cli_frontendtidy-ups. An interface forCli_frontend— which alsodocuments
parse_commandline's type change — plus a simplerparse_commandlineand removal of the dead
rio_help. Unrelated to the feature; done first so theystay out of the later diffs.
4–7 — preparation for commit 8. The parts of the parameter-handling code
that are not a straight substitution:
params_except,override_paramandassoc_default_cibecome helpers, and thevm_migrateoptions block isrewritten. Isolating them here lets commit 8 be purely mechanical, with nothing
subtle folded in.
8 — route every parameter access through
Cli_args. The largest diff, and apure mechanical substitution — no behaviour change.
List.assoc "x" paramsbecomes
Cli_args.get "x" params, and so on, acrosscli_operations.ml,cli_frontend.mlandxapi_cli.ml. The type is abstract;opincli_cmdtablenow carries
string Cli_args.tinstead of(string * string) list.9–11 — map/set plumbing, still no behaviour change:
Cli_args.view: a lens onto theprefix:key=valuemap/set entries (and theold
prefix-key=valueform).Cli_maps: one named view per map/set parameter, and the one place that liststhem all.
~keyforchoose_params/select_fields, sovm_disk_list_auxstops rebuilding the parameter list just to rename
vbd-params/vdi-params.12 — track reads. Adds the
usedflag and the marking rules from Designchoices. Wholesale consumers call
Cli_args.consume.vm_migrate's parameteraliasing switches to
Cli_args.rename_key, which edits entries in place; the oldcode round-tripped through
to_pairs/from_pairsand lost the tracking.Cli_args.unusedis defined here but not yet called, so nothing observablechanges.
13 — unit tests for the tracking engine (
ocaml/tests/test_cli_args.ml, insuite_alcotest): which accessors mark, marking seen through a view or a derivedstructure, prefix stripping,
consume/mark_used/rename_key, and thatunusedreturns exactly the untouched keys.14 — the report.
report_ignored_paramscallsunusedand prints, from afinallyaround the command indo_rpcs, so it fires whether the commandsucceeded or failed. Gated on
report-ignored-params=warnon the invocation(also settable via
XE_EXTRA_ARGS/~/.xe).15 — add
xapi.confas a second place to switch it on. Adds thereport-ignored-paramskey toxapi.confas a fallback, consulted only when theparameter is absent from the invocation.