All of lore.kernel.org
 help / color / mirror / Atom feed
From: John Snow <jsnow@redhat.com>
To: Damien Hedde <damien.hedde@greensocs.com>
Cc: Eduardo Habkost <eduardo@habkost.net>,
	qemu-devel <qemu-devel@nongnu.org>,
	Cleber Rosa <crosa@redhat.com>
Subject: Re: [PATCH 4/5] python: qmp_shell: add -e/--exit-on-error option
Date: Wed, 23 Feb 2022 10:22:11 -0500	[thread overview]
Message-ID: <CAFn=p-ZErejiw7mn_kN6c+57ya4OgS3ANpUa=BtS3Be=vcSOvg@mail.gmail.com> (raw)
In-Reply-To: <20220221155519.2367-5-damien.hedde@greensocs.com>

On Mon, Feb 21, 2022 at 10:55 AM Damien Hedde
<damien.hedde@greensocs.com> wrote:
>
> This option makes qmp_shell exit (with error code 1)
> as soon as one of the following error occurs:
> + command parsing error
> + disconnection
> + command failure (response is an error)
>
> _execute_cmd() method now returns None or the response
> so that read_exec_command() can do the last check.
>
> This is meant to be used in combination with an input file
> redirection. It allows to store a list of commands
> into a file and try to run them by qmp_shell and easily
> see if it failed or not.
>
> Signed-off-by: Damien Hedde <damien.hedde@greensocs.com>

Based on this patch, it looks like you really want something
scriptable, so I think the qemu-send idea that Dan has suggested might
be the best way to go. Are you still hoping to use the interactive
"short" QMP command format? That might be a bad idea, given how flaky
the parsing is -- and how we don't actually have a published standard
for that format. We've *never* liked the bad parsing here, so I have a
reluctance to use it in more places.

I'm having the naive idea that a script file could be as simple as a
list of QMP commands to send:

[
    {"execute": "block-dirty-bitmap-add", "arguments": { ... }},
    ...
]

And a processing script could be something as simple as:

~~~
with open("script.json") as infile:
    script = json.load(infile)

for command in script:
    await qmp.execute_msg(command)
~~~


It's very simple to do, but the script file format is now a bit more
chatty than it used to be. You could also elide the "execute" and
"arguments" keys, perhaps:

[
    {"block-dirty-bitmap-add": {"name": ..., "node": ...},
    ...
]

And then the script only changes a little bit:

for item in script:
    for cmd, args in item.items():
        await qmp.execute(cmd, args)

but this might lose the ability to opt into "execute-oob" as one
consequence of a more terse script format.



> ---
>  python/qemu/aqmp/qmp_shell.py | 39 +++++++++++++++++++++++++----------
>  1 file changed, 28 insertions(+), 11 deletions(-)
>
> diff --git a/python/qemu/aqmp/qmp_shell.py b/python/qemu/aqmp/qmp_shell.py
> index cce7732ba2..dd38ef8a13 100644
> --- a/python/qemu/aqmp/qmp_shell.py
> +++ b/python/qemu/aqmp/qmp_shell.py
> @@ -11,7 +11,7 @@
>  """
>  Low-level QEMU shell on top of QMP.
>
> -usage: qmp-shell [-h] [-H] [-N] [-v] [-p] qmp_server
> +usage: qmp-shell [-h] [-H] [-N] [-v] [-p] [-e] qmp_server
>
>  positional arguments:
>    qmp_server            < UNIX socket path | TCP address:port >
> @@ -23,6 +23,8 @@
>                          Skip negotiate (for qemu-ga)
>    -v, --verbose         Verbose (echo commands sent and received)
>    -p, --pretty          Pretty-print JSON
> +  -e, --exit-on-error   Exit when an error occurs (command parsing,
> +                        disconnection and command failure)
>
>
>  Start QEMU with:
> @@ -177,9 +179,11 @@ class QMPShell(QEMUMonitorProtocol):
>      :param address: Address of the QMP server.
>      :param pretty: Pretty-print QMP messages.
>      :param verbose: Echo outgoing QMP messages to console.
> +    :param raise_error: Don't continue after an error occured
>      """
>      def __init__(self, address: SocketAddrT,
> -                 pretty: bool = False, verbose: bool = False):
> +                 pretty: bool = False, verbose: bool = False,
> +                 raise_error: bool = False):
>          super().__init__(address)
>          self._greeting: Optional[QMPMessage] = None
>          self._completer = QMPCompleter()
> @@ -189,6 +193,7 @@ def __init__(self, address: SocketAddrT,
>                                        '.qmp-shell_history')
>          self.pretty = pretty
>          self.verbose = verbose
> +        self.raise_error = raise_error
>
>      def close(self) -> None:
>          # Hook into context manager of parent to save shell history.
> @@ -343,19 +348,19 @@ def _print_parse_error(self, err: QMPShellParseError) -> None:
>              file=sys.stderr
>          )
>
> -    def _execute_cmd(self, cmdline: str) -> bool:
> +    def _execute_cmd(self, cmdline: str) -> Optional[QMPMessage]:
>          qmpcmd = self._build_cmd(cmdline)
>
>          # For transaction mode, we may have just cached the action:
>          if qmpcmd is None:
> -            return True
> +            return None
>          if self.verbose:
>              self._print(qmpcmd)
>          resp = self.cmd_obj(qmpcmd)
>          if resp is None:
>              raise QMPShellConnectError('Disconnected')
>          self._print(resp)
> -        return True
> +        return resp
>
>      def connect(self, negotiate: bool = True) -> None:
>          self._greeting = super().connect(negotiate)
> @@ -401,8 +406,13 @@ def read_exec_command(self) -> bool:
>                  print(event)
>              return True
>
> +        if self.raise_error:
> +            resp = self._execute_cmd(cmdline)
> +            if resp and 'error' in resp:
> +                raise QMPShellError(f"Command failed: {resp['error']}")
> +            return True
>          try:
> -            return self._execute_cmd(cmdline)
> +            self._execute_cmd(cmdline)
>          except QMPShellParseError as err:
>              self._print_parse_error(err)
>          except QMPShellConnectError as err:
> @@ -477,7 +487,7 @@ def _cmd_passthrough(self, cmdline: str,
>      def _print_parse_error(self, err: QMPShellParseError) -> None:
>          print(f"{err!s}")
>
> -    def _execute_cmd(self, cmdline: str) -> bool:
> +    def _execute_cmd(self, cmdline: str) -> Optional[QMPMessage]:
>          if cmdline.split()[0] == "cpu":
>              # trap the cpu command, it requires special setting
>              try:
> @@ -498,7 +508,7 @@ def _execute_cmd(self, cmdline: str) -> bool:
>          else:
>              # Error
>              print('%s: %s' % (resp['error']['class'], resp['error']['desc']))
> -        return True
> +        return resp
>
>      def show_banner(self, msg: str = 'Welcome to the HMP shell!') -> None:
>          QMPShell.show_banner(self, msg)
> @@ -523,6 +533,9 @@ def main() -> None:
>                          help='Verbose (echo commands sent and received)')
>      parser.add_argument('-p', '--pretty', action='store_true',
>                          help='Pretty-print JSON')
> +    parser.add_argument('-e', '--exit-on-error', action='store_true',
> +                        help='Exit when an error occurs (command parsing,'
> +                             ' disconnection and command failure)')
>
>      default_server = os.environ.get('QMP_SOCKET')
>      parser.add_argument('qmp_server', action='store',
> @@ -541,7 +554,8 @@ def main() -> None:
>          parser.error(f"Bad port number: {args.qmp_server}")
>          return  # pycharm doesn't know error() is noreturn
>
> -    with shell_class(address, args.pretty, args.verbose) as qemu:
> +    with shell_class(address, args.pretty, args.verbose,
> +                     args.exit_on_error) as qemu:
>          try:
>              qemu.connect(negotiate=not args.skip_negotiation)
>          except ConnectError as err:
> @@ -549,8 +563,11 @@ def main() -> None:
>                  die(f"Couldn't connect to {args.qmp_server}: {err!s}")
>              die(str(err))
>
> -        for _ in qemu.repl():
> -            pass
> +        try:
> +            for _ in qemu.repl():
> +                pass
> +        except QMPShellError as err:
> +            die(str(err))
>
>
>  if __name__ == '__main__':
> --
> 2.35.1
>



  reply	other threads:[~2022-02-23 15:23 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-02-21 15:55 [PATCH 0/5] qmp-shell modifications for non-interactive use Damien Hedde
2022-02-21 15:55 ` [PATCH 1/5] python: qmp_shell: don't prompt when stdin is non-interactive Damien Hedde
2022-02-21 15:55 ` [PATCH 2/5] python: qmp_shell: refactor the parsing error handling Damien Hedde
2022-02-21 15:55 ` [PATCH 3/5] python: qmp_shell: refactor disconnection handling Damien Hedde
2022-02-21 15:55 ` [PATCH 4/5] python: qmp_shell: add -e/--exit-on-error option Damien Hedde
2022-02-23 15:22   ` John Snow [this message]
2022-02-23 15:27     ` Daniel P. Berrangé
2022-02-23 15:41       ` John Snow
2022-02-23 15:44         ` Daniel P. Berrangé
2022-02-23 16:18           ` John Snow
2022-02-23 17:09             ` Damien Hedde
2022-02-23 18:20               ` John Snow
2022-02-24 11:20                 ` Damien Hedde
2022-02-23 17:50             ` Daniel P. Berrangé
2022-02-23 16:43           ` Damien Hedde
2022-02-23 16:46             ` Damien Hedde
2022-02-21 15:55 ` [PATCH 5/5] python: qmp_shell: handle comment lines and escaped eol Damien Hedde
2022-02-22  6:10 ` [PATCH 0/5] qmp-shell modifications for non-interactive use Markus Armbruster
2022-02-22  7:57   ` Damien Hedde
2022-02-22  9:21     ` Daniel P. Berrangé
2022-02-22  9:38       ` Damien Hedde
2022-02-22 10:31         ` Daniel P. Berrangé
2022-02-23  9:57           ` Damien Hedde
2022-02-23 11:13             ` Daniel P. Berrangé
2022-02-23 15:01               ` John Snow
2022-02-23 15:08                 ` Daniel P. Berrangé
2022-02-22  9:52     ` Markus Armbruster
2022-02-25 20:40 ` John Snow

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAFn=p-ZErejiw7mn_kN6c+57ya4OgS3ANpUa=BtS3Be=vcSOvg@mail.gmail.com' \
    --to=jsnow@redhat.com \
    --cc=crosa@redhat.com \
    --cc=damien.hedde@greensocs.com \
    --cc=eduardo@habkost.net \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.