After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 767999 - [review] lr/device-modify: Add live editing of the applied connection
[review] lr/device-modify: Add live editing of the applied connection
Status: RESOLVED FIXED
Product: NetworkManager
Classification: Platform
Component: general
git master
Other Linux
: Normal normal
: ---
Assigned To: NetworkManager maintainer(s)
NetworkManager maintainer(s)
Depends on:
Blocks:
 
 
Reported: 2016-06-24 10:24 UTC by Lubomir Rintel
Modified: 2016-06-29 18:28 UTC
See Also:
GNOME target: ---
GNOME version: ---



Description Lubomir Rintel 2016-06-24 10:24:42 UTC
The actual command is preceded by some fixes & refactorings; basically to make tab completion implementation easier:

f604146 cli/connections: only do completion for the last argument
f04468b cli/device: fix --help in usage
1122924 cli/devices: rename some functions for consistency
338b9df cli: move the final completion check after the main loop exit
3b305d8 cli: use should_wait consistently
eb175d8 cli: split out do_cmd()
2e6f068 cli/device: use nmc_do_cmd()
c48e405 cli/device: rename device_list() to get_device_list()
4ac407f cli/device: split get_device()
e33c039 cli/device: add device name completion

This is needed for the actual d modify:

2594379 cli/connection: export read_connection_properties()
4d65391 cli/device: add modify command
Comment 1 Thomas Haller 2016-06-24 13:45:03 UTC
>> cli/devices: rename some functions for consistency

subject should contain the keyword "trivial" (which always causes me to look at `git diff --color-words=.`).

  "cli/trivial: rename internal functions"


>> cli: split out do_cmd()

+nmc_do_cmd (NmCli *nmc, NMCCommand cmds[], const char *cmd, int argc, char  
                         ^^^ const


+static NMCCommand nmcli_cmds[] = {
      ^^^^ const


+typedef struct {
+        const char *cmd;
+        NMCResultCode (*func) (NmCli *nmc, int argc, char **argv);
+    void (*usage) (voi
indention



>> cli/device: use nmc_do_cmd()

-         nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
-         goto error;
+         return NMC_RESULT_ERROR_USER_INPUT;

good, dropping return_value, but let's just remove it entirely.


+static NMCCommand device_cmds[] = {
        ^^^^ const

>> cli/device: rename device_list() to get_device_list()

trivial



>> cli/device: split get_device()
    
Parsing the a single device name
       ^^^^^^^^
    
Shouldn't we support

nmcli device <CMD> [TYPE] <DEVICE>

like
nmcli device disconnect ifname eth0
nmcli device disconnect path /org/fd/NM/Devices/5
nmcli device disconnect path 5
nmcli device disconnect ifindex 42
nmcli device disconnect apath /org/fg/NM/ActiveConnection/7
nmcli device disconnect apath 7

like we also have `nmcli connection show [id|path|uuid] <CONNECTION>`.




+    NMDevice *device = get_device (nmc, &argc, &argv);

     ...

+    if (device) {
          show_device_info (device, nmc);
     } else {
+         NMDevice **devices = get_devices_sorted (nmc->client);
+         int i;


for `nmcli device show`, get_device will return %NULL and we show all devices (good). But in this case, get_device() will also do:
+    if (*argc == 0) {
+         if (nmc->ask)
+              ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE);
+
+         if (!ifname_ask) {
+              g_string_printf (nmc->return_text, _("Error: No interfa...
+              nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+              return NULL;
+         }

I think, get_device() should have a argument "allow_none", or alternatively you do
   device = argc > 0 ? get_device (nmc, &argc, &argv) : NULL;



In get_device(), the following if() is always FALSE (unless bug).
+    if (!ifname) {
+         g_string_printf (nmc->return_text, _("Error: No interface specified."));
+         nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+         return NULL;
+    }



>> cli/device: add device name completion

- !(nm_streq0 (argv[2], "connection") || nm_streq0 (argv[2], "device"))
+ !NM_IN_STRSET (argv[1], "connection", "device")



 static NMCResultCode
 do_device_wifi (NmCli *nmc, int argc, char **argv)
 {
     GError *error = NULL;
 
+    /* Not (yet?) supported */
+    if (nmc->complete)
+         return nmc->return_value;

instead of adding this check to ever function, NMCCommand could have a field "supports_completion" and nmc_do_cmd() could return right away. I like that more, because then functions that support completion have to opt-in, instead of each one individually check and error out.
If you also set the flag in the toplevel nmcli_cmds, then you can drop the check
»···»···if ((argc == 2) || !(nm_streq0 (argv[2], "connection") || nm_
»···»···»···return nmc->return_value;



>> cli/connection: export read_connection_properties()
    
I know, nmcli is not consistent there, but could we give every function in header files a nm* prefix? For clients/cli/connections.h, a possible prefix would be nmc_connections_*(), and then call it nmc_connections_read_properties().


>> cli/device: add modify command
    
+                  "the connection profile. The changes have immediate effect. For multi-valued\n"
indention.




+                    mod|modi|modif|modify)
+                        if [[ ${#words[@]} -eq 3 ]]; then
+                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(nmcli --complete-args connection add "" 2>/dev/null)"
+                        else
+                            _nmcli_array_delete_at words 0 1
+                            _nmcli_list_nl "$(nmcli --complete-args connection add "${words[@]}" 2>/dev/null)"

^^ "connection add" above.




+                    mod|modi|modif|modify)
+                    mon|moni|monit|monito|monitor)
you change the meaning of `nmcli device "m"|"mo"`. They should continue to mean "monitor". For that, you also must order "modify" after "monitor" in device_cmds.



+          <para>This command lets you do temporary changes to a connection active on
+          a particular device without preserving them.</para>


that makes it sound, like you are modifying the (settings) connection, but I have no better suggestion for now.




+
+    if (!read_connection_properties (info->nmc, connection, &info->argc, &info->argv, &error)) {
+                g_string_assign (nmc->return_text, error->message);
+                nmc->return_valu

indention.


+         g_error_free (error);

gs_free_error?





As a follow-up, for the modify command, we actually only need to read the list of devices (unless we implement `nmcli device modify apath /o/fd/NM/ActiveConnection/7`, in which case we first would have to search the proper ActiveConnection). Anyway, the point is, this *load* everything slows us so much down although for many cases we don't actually need it.


As another follow-up, could we step by step get rid of the fields in NmCli struct? First of all, the return_value seems wrong, why do we even set a global variable return_value, instead of having every function returning it's result? (which we already often do as well ("return nmc->return_value;")



Otherwise, very nice branch.
Comment 2 Lubomir Rintel 2016-06-24 17:55:30 UTC
>>> cli/trivial: rename some functions for consistency
>
>subject should contain the keyword "trivial" (which always causes me to look at `git diff --color-words=.`).
>
>  "cli/trivial: rename internal functions"

Oh, learned something. I didn't know color-words.

>>> cli: split out do_cmd()
>
>+nmc_do_cmd (NmCli *nmc, NMCCommand cmds[], const char *cmd, int argc, char
>                         ^^^ const
>
>
>+static NMCCommand nmcli_cmds[] = {
>      ^^^^ const

Fixed.

>+typedef struct {
>+        const char *cmd;
>+        NMCResultCode (*func) (NmCli *nmc, int argc, char **argv);
>+    void (*usage) (voi
>indention

Fixed.

>>> cli/device: use nmc_do_cmd()
>
>-         nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
>-         goto error;
>+         return NMC_RESULT_ERROR_USER_INPUT;
>
>good, dropping return_value, but let's just remove it entirely.

Well; I'll gladly apply a fixup ;)

>+static NMCCommand device_cmds[] = {
>        ^^^^ const

Fixed.

>>> cli/device: rename device_list() to get_device_list()
>
>trivial

Ok.

>>> cli/device: split get_device()
>
>Parsing the a single device name
>       ^^^^^^^^

Fixed

>Shouldn't we support
>
>nmcli device <CMD> [TYPE] <DEVICE>
>
>like
>nmcli device disconnect ifname eth0
>nmcli device disconnect path /org/fd/NM/Devices/5
>nmcli device disconnect path 5
>nmcli device disconnect ifindex 42
>nmcli device disconnect apath /org/fg/NM/ActiveConnection/7
>nmcli device disconnect apath 7
>
>like we also have `nmcli connection show [id|path|uuid] <CONNECTION>`.

Maybe. We definitely don't support that in general. Some subcommands take
"ifname", some do optionally.

===
I'll finish addressing the review now, since the comments seem too complicated. Will get a beer now instead and see if they make more sense afterwards.
Comment 3 Lubomir Rintel 2016-06-27 10:32:39 UTC
>>> cli/device: split get_device()

>+    NMDevice *device = get_device (nmc, &argc, &argv);
>
>     ...
>
>+    if (device) {
>          show_device_info (device, nmc);
>     } else {
>+         NMDevice **devices = get_devices_sorted (nmc->client);
>+         int i;
>
>
>for `nmcli device show`, get_device will return %NULL and we show all devices (good). But in this case, get_device() will also do:
>+    if (*argc == 0) {
>+         if (nmc->ask)
>+              ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE);
>+
>+         if (!ifname_ask) {
>+              g_string_printf (nmc->return_text, _("Error: No interfa...
>+              nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
>+              return NULL;
>+         }
>
>I think, get_device() should have a argument "allow_none", or alternatively you do
>   device = argc > 0 ? get_device (nmc, &argc, &argv) : NULL;

Yes, the second option seems better to me.


>In get_device(), the following if() is always FALSE (unless bug).
>+    if (!ifname) {
>+         g_string_printf (nmc->return_text, _("Error: No interface specified."));
>+         nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
>+         return NULL;
>+    }

Right. Removed.

>>> cli/device: add device name completion
>
>- !(nm_streq0 (argv[2], "connection") || nm_streq0 (argv[2], "device"))
>+ !NM_IN_STRSET (argv[1], "connection", "device")

The same thing. It's ideally going away soon rather than later :(

> static NMCResultCode
> do_device_wifi (NmCli *nmc, int argc, char **argv)
> {
>     GError *error = NULL;
>
>+    /* Not (yet?) supported */
>+    if (nmc->complete)
>+         return nmc->return_value;
>
>instead of adding this check to ever function, NMCCommand could have a field "supports_completion" and nmc_do_cmd() could return right away. I like that more, because then functions that support completion have to opt-in, instead of each one individually check and error out.
>If you also set the flag in the toplevel nmcli_cmds, then you can drop the check
>»···»···if ((argc == 2) || !(nm_streq0 (argv[2], "connection") || nm_
>»···»···»···return nmc->return_value;

I don't know. It's supposed to go away soon and makes it easy to spot where completion needs to be finished.

>>> cli/connection: export read_connection_properties()
>
>I know, nmcli is not consistent there, but could we give every function in header files a nm* prefix? For clients/cli/connections.h, a possible prefix would be nmc_connections_*(), and then call it nmc_connections_read_properties().

Sounds like a good idea. Done.

>>> cli/device: add modify command
>
>+                  "the connection profile. The changes have immediate effect. For multi-valued\n"
>indention.

Fixed.

>+                    mod|modi|modif|modify)
>+                        if [[ ${#words[@]} -eq 3 ]]; then
>+                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(nmcli --complete-args connection add "" 2>/dev/null)"
>+                        else
>+                            _nmcli_array_delete_at words 0 1
>+                            _nmcli_list_nl "$(nmcli --complete-args connection add "${words[@]}" 2>/dev/null)"
>
>^^ "connection add" above.

Fixed.

>+                    mod|modi|modif|modify)
>+                    mon|moni|monit|monito|monitor)
>you change the meaning of `nmcli device "m"|"mo"`. They should continue to mean "monitor". For that, you also must order "modify" after "monitor" in device_cmds.

Fixed.

>+          <para>This command lets you do temporary changes to a connection active on
>+          a particular device without preserving them.</para>
>
>
>that makes it sound, like you are modifying the (settings) connection, but I have no better suggestion for now.

Yes, spent some time figuring out the wording :( I've now changed it yet again, hopefully it's better now.

>+
>+    if (!read_connection_properties (info->nmc, connection, &info->argc, &info->argv, &error)) {
>+                g_string_assign (nmc->return_text, error->message);
>+                nmc->return_valu
>
>indention.
>
>
>+         g_error_free (error);
>
>gs_free_error?

Hmm, why not.

>As a follow-up, for the modify command, we actually only need to read the list of devices (unless we implement `nmcli device modify apath /o/fd/NM/ActiveConnection/7`, in which case we first would have to search the proper ActiveConnection). Anyway, the point is, this *load* everything slows us so much down although for many cases we don't actually need it.
>
>
>As another follow-up, could we step by step get rid of the fields in NmCli struct? First of all, the return_value seems wrong, why do we even set a global variable return_value, instead of having every function returning it's result? (which we already often do as well ("return nmc->return_value;")

Well, list of the possible follow-ups seems infinite to me now...

I intend to improve the completion coverage first; replace all sub-command parsing with nmc_do_cmd() first. That's going to make the rest easier anyway.

>Otherwise, very nice branch.
Comment 4 Lubomir Rintel 2016-06-27 10:51:24 UTC
Updated the branch.

I do have more follow ups; but I'd prefer not to delay merge of the modify subcommand for the unrelated completions -- opened bug #768089 for that.
Comment 5 Thomas Haller 2016-06-27 17:14:31 UTC
>> cli/device: split get_device()

get_device() is new code that makes heavy use of @nmc argument for no reason.
If we ever hope to get rid of that, we should avoid that.
The pair nmc->return_text and nmc->return_value should be replaced by a GError, possibly with a new special error domain "NMC_PROGRAM_ERROR" where the error codes directly map to the NMC_RESULT_ERROR_* values.

 static NMDevice *
-get_device (NmCli *nmc, int *argc, char ***argv)
+get_device (NMClient *client, gboolean ask, int *argc, char ***argv, GError **error)



rest lgtm
Comment 6 Beniamino Galvani 2016-06-28 20:24:13 UTC
> cli/device: fix --help in usage

This seems inconsistent with other commands' syntax, e.g:

Usage: nmcli [OPTIONS] OBJECT { COMMAND | help }
Usage: nmcli connection { COMMAND | help }
Usage: nmcli radio { COMMAND | help }

If it needs to be fixed, I think it should be done everywhere.


> cli/device: add modify command

+  mod|modi|modif|modify)
+      if [[ ${#words[@]} -eq 3 ]]; then
+          _nmcli_compl_COMMAND_nl "${words[2]}" "$(nmcli --complete-args connection add "" 2>/dev/null)"

"connection add" -> "device modify"?

+      else
+          _nmcli_array_delete_at words 0 1
+          _nmcli_list_nl "$(nmcli --complete-args connection modify "${words[@]}" 2>/dev/null)"

"connection" -> "device" ?


Otherwise LGTM.
Comment 7 Lubomir Rintel 2016-06-29 18:28:09 UTC
(In reply to Thomas Haller from comment #5)
> >> cli/device: split get_device()
> 
> get_device() is new code that makes heavy use of @nmc argument for no reason.
> If we ever hope to get rid of that, we should avoid that.
> The pair nmc->return_text and nmc->return_value should be replaced by a
> GError, possibly with a new special error domain "NMC_PROGRAM_ERROR" where
> the error codes directly map to the NMC_RESULT_ERROR_* values.
> 
>  static NMDevice *
> -get_device (NmCli *nmc, int *argc, char ***argv)
> +get_device (NMClient *client, gboolean ask, int *argc, char ***argv, GError
> **error)

Fixed the error handling. The nmc thing for the global flag is consistent with what we do elsewhere. Turning the global flags into function arguments seems outside of the scope of this branch if it even is a good idea.

(In reply to Beniamino Galvani from comment #6)
> > cli/device: fix --help in usage
> 
> This seems inconsistent with other commands' syntax, e.g:
> 
> Usage: nmcli [OPTIONS] OBJECT { COMMAND | help }
> Usage: nmcli connection { COMMAND | help }
> Usage: nmcli radio { COMMAND | help }
> 
> If it needs to be fixed, I think it should be done everywhere.

Dropped this patch. It seems like this is in fact the correct syntax. Good luck to the user that has a device called "help"...

> > cli/device: add modify command
> 
> +  mod|modi|modif|modify)
> +      if [[ ${#words[@]} -eq 3 ]]; then
> +          _nmcli_compl_COMMAND_nl "${words[2]}" "$(nmcli --complete-args
> connection add "" 2>/dev/null)"
> 
> "connection add" -> "device modify"?
> 
> +      else
> +          _nmcli_array_delete_at words 0 1
> +          _nmcli_list_nl "$(nmcli --complete-args connection modify
> "${words[@]}" 2>/dev/null)"
> 
> "connection" -> "device" ?

Fixed.