Skip to contentSkip to conversation

scripts: qmp-shell: add transaction support

PATCH Superseded John Snow 2 participants 17 messages Apr 22, 2015

v1 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

John Snow

author
#0 lore raw

cover scripts: qmp-shell: add transaction support

The qmp-shell is a little rudimentary, but it can be hacked to give us some transactional support without too much difficulty.

(1) Prep.
(2) Add support for serializing json arrays
(3) Allow users to use 'single quotes' instead of "double quotes"
(4) Add a special transaction( ... ) syntax that lets users build up transactional commands using the existing qmp shell syntax to define each action.
(5) Add a verbose flag to display generated QMP commands.

The parsing is not as robust as one would like, but this suffices without adding a proper parser.

Design considerations:

(1) Try not to disrupt the existing design of the qmp-shell. The existing API is not disturbed.

(2) Pick a "magic token" such that it could not be confused for legitimate QMP/JSON syntax. Parentheses are used for this purpose.

John Snow (5):
scripts: qmp-shell: refactor helpers
scripts: qmp-shell: add support for [] expressions
scripts: qmp-shell: allow single-quotes in JSON expressions
scripts: qmp-shell: add transaction subshell
scripts: qmp-shell: Add verbose flag

 scripts/qmp/qmp-shell | 90 +++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 73 insertions(+), 17 deletions(-)
Signature · 1 line
2.1.0
author
#1 lore raw

PATCH 1/5 scripts: qmp-shell: refactor helpers

Refactor the qmp-shell command line processing function into two components. This will be used to allow sub-expressions, which will assist us in adding transactional support to qmp-shell.

---
 scripts/qmp/qmp-shell | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)
scripts/qmp/qmp-shell +13 -11
index e0e848b..a9632ec 100755
@@ -88,16 +88,8 @@ class QMPShell(qmp.QEMUMonitorProtocol):
         # clearing everything as it doesn't seem to matter
         readline.set_completer_delims('')
 
-    def __build_cmd(self, cmdline):
-        """
-        Build a QMP input object from a user provided command-line in the
-        following format:
-
-            < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
-        """
-        cmdargs = cmdline.split()
-        qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
-        for arg in cmdargs[1:]:
+    def __cli_expr(self, tokens, parent):
+        for arg in tokens:
             opt = arg.split('=')
             try:
                 if(len(opt) > 2):
@@ -113,7 +105,6 @@ class QMPShell(qmp.QEMUMonitorProtocol):
                 else:
                     value = opt[1]
             optpath = opt[0].split('.')
-            parent = qmpcmd['arguments']
             curpath = []
             for p in optpath[:-1]:
                 curpath.append(p)
@@ -128,6 +119,17 @@ class QMPShell(qmp.QEMUMonitorProtocol):
                 else:
                     raise QMPShellError('Cannot set "%s" multiple times' % opt[0])
             parent[optpath[-1]] = value
+
+    def __build_cmd(self, cmdline):
+        """
+        Build a QMP input object from a user provided command-line in the
+        following format:
+
+            < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
+        """
+        cmdargs = cmdline.split()
+        qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
+        self.__cli_expr(cmdargs[1:], qmpcmd['arguments'])
         return qmpcmd
 
     def _execute_cmd(self, cmdline):
Signature · 1 line
2.1.0
Reviewed-by on v1 1/5
#2 lore raw

Re: [Qemu-devel] [PATCH 1/5] scripts: qmp-shell: refactor helpers

Quoted 9 lines · John Snow

Refactor the qmp-shell command line processing function into two components. This will be used to allow sub-expressions, which will assist us in adding transactional support to qmp-shell.

---
 scripts/qmp/qmp-shell | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)
Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#3 lore raw

PATCH 2/5 scripts: qmp-shell: add support for [] expressions

In reply to John Snow

qmp-shell currently allows you to describe values as JSON expressions:

key={"key":{"key2":"val"}}

But it does not currently support arrays, which are needed for serializing and deserializing transactions:

key=[{"type":"drive-backup","data":{...}}]

Add support for arrays.

CAVEAT: The parser is still extremely rudimentary and does not expect to find spaces in {} nor [] expressions. This patch does not improve this functionality.

---
 scripts/qmp/qmp-shell | 2 ++
 1 file changed, 2 insertions(+)
scripts/qmp/qmp-shell +2 -0
index a9632ec..5347f89 100755
@@ -102,6 +102,8 @@ class QMPShell(qmp.QEMUMonitorProtocol):
                     value = False
                 elif opt[1].startswith('{'):
                     value = json.loads(opt[1])
+                elif opt[1].startswith('['):
+                    value = json.loads(opt[1])
                 else:
                     value = opt[1]
             optpath = opt[0].split('.')
Signature · 1 line
2.1.0
#4 lore raw

Re: [Qemu-devel] [PATCH 2/5] scripts: qmp-shell: add support for [] expressions

Quoted 26 lines · John Snow

qmp-shell currently allows you to describe values as JSON expressions:

key={"key":{"key2":"val"}}

But it does not currently support arrays, which are needed for serializing and deserializing transactions:

key=[{"type":"drive-backup","data":{...}}]

Add support for arrays.

CAVEAT: The parser is still extremely rudimentary and does not expect to find spaces in {} nor [] expressions. This patch does not improve this functionality.

---
 scripts/qmp/qmp-shell | 2 ++
 1 file changed, 2 insertions(+)
scripts/qmp/qmp-shell +0 -0
index a9632ec..5347f89 100755

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 106

Quoted 5 lines
scripts/qmp/qmp-shell +2 -0
@@ -102,6 +102,8 @@ class QMPShell(qmp.QEMUMonitorProtocol):
                     value = False
                 elif opt[1].startswith('{'):
                     value = json.loads(opt[1])
+                elif opt[1].startswith('['):
+                    value = json.loads(opt[1])

Why not:

elif opt[1].startswith('{'} or opt[1].startswith('['):

for a one-line change instead of two-line addition? And I'm no python expert, but I think you can write that with fewer characters. Untested, but does this work?

elif opt[1][0] in "{[":

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#5 lore raw

Re: [Qemu-devel] [PATCH 2/5] scripts: qmp-shell: add support for [] expressions

Quoted 35 lines · Eric Blake
Quoted 26 lines · John Snow

qmp-shell currently allows you to describe values as JSON expressions:

key={"key":{"key2":"val"}}

But it does not currently support arrays, which are needed for serializing and deserializing transactions:

key=[{"type":"drive-backup","data":{...}}]

Add support for arrays.

CAVEAT: The parser is still extremely rudimentary and does not expect to find spaces in {} nor [] expressions. This patch does not improve this functionality.

---
  scripts/qmp/qmp-shell | 2 ++
  1 file changed, 2 insertions(+)
scripts/qmp/qmp-shell +0 -0
index a9632ec..5347f89 100755

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 106

Quoted 5 lines
scripts/qmp/qmp-shell +2 -0
@@ -102,6 +102,8 @@ class QMPShell(qmp.QEMUMonitorProtocol):
                      value = False
                  elif opt[1].startswith('{'):
                      value = json.loads(opt[1])
+                elif opt[1].startswith('['):
+                    value = json.loads(opt[1])

Why not:

Quoted 2 lines

elif opt[1].startswith('{'} or opt[1].startswith('['):

Didn't really consider this patch too long is why :)
If the rest of it looks worth checking in to you, I'll make the tidying edits.

Quoted 6 lines

for a one-line change instead of two-line addition? And I'm no python expert, but I think you can write that with fewer characters. Untested, but does this work?

elif opt[1][0] in "{[":

It should.

Thanks,
--js

author
#6 lore raw

PATCH 3/5 scripts: qmp-shell: allow single-quotes in JSON expressions

In reply to John Snow

As a convenience for the user, replace any single quotes given with double quotes so that the data will deserialize correctly via json.loads().

---
 scripts/qmp/qmp-shell | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
scripts/qmp/qmp-shell +1 -1
index 5347f89..d7cb33d 100755
@@ -129,7 +129,7 @@ class QMPShell(qmp.QEMUMonitorProtocol):
 
             < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
         """
-        cmdargs = cmdline.split()
+        cmdargs = cmdline.replace("'", '"').split()
         qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
         self.__cli_expr(cmdargs[1:], qmpcmd['arguments'])
         return qmpcmd
Signature · 1 line
2.1.0
Reviewed-by on v1 3/5
#7 lore raw

Re: [Qemu-devel] [PATCH 3/5] scripts: qmp-shell: allow single-quotes in JSON expressions

Quoted 14 lines · John Snow

As a convenience for the user, replace any single quotes given with double quotes so that the data will deserialize correctly via json.loads().

---
 scripts/qmp/qmp-shell | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
scripts/qmp/qmp-shell +0 -0
index 5347f89..d7cb33d 100755

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 132

Quoted 5 lines
scripts/qmp/qmp-shell +1 -1
@@ -129,7 +129,7 @@ class QMPShell(qmp.QEMUMonitorProtocol):
 
             < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
         """
-        cmdargs = cmdline.split()
+        cmdargs = cmdline.replace("'", '"').split()

This replaces ALL single quotes, even if they would otherwise be escaped. That is, if I pass foo="a\'\"b", it will probably be corrupted.

qmp-shell exists mainly as a convenience for testing, and I doubt testers are likely to want to use unbalanced quotes as contents of strings, so I can give a weak:

But it's still worth thinking about whether there is a more robust solution to be used.

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#8 lore raw

Re: [Qemu-devel] [PATCH 3/5] scripts: qmp-shell: allow single-quotes in JSON expressions

Quoted 24 lines · Eric Blake
Quoted 14 lines · John Snow

As a convenience for the user, replace any single quotes given with double quotes so that the data will deserialize correctly via json.loads().

---
  scripts/qmp/qmp-shell | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)
scripts/qmp/qmp-shell +0 -0
index 5347f89..d7cb33d 100755

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 132

Quoted 5 lines
scripts/qmp/qmp-shell +1 -1
@@ -129,7 +129,7 @@ class QMPShell(qmp.QEMUMonitorProtocol):
 
              < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
          """
-        cmdargs = cmdline.split()
+        cmdargs = cmdline.replace("'", '"').split()

This replaces ALL single quotes, even if they would otherwise be escaped. That is, if I pass foo="a\'\"b", it will probably be corrupted.

Quoted 8 lines

qmp-shell exists mainly as a convenience for testing, and I doubt testers are likely to want to use unbalanced quotes as contents of strings, so I can give a weak:

But it's still worth thinking about whether there is a more robust solution to be used.

Oh, hmm. I naively assumed that such quotes weren't permissable within the namespace for any valid QMP commands, but...

There's a lot of simplifying assumptions within qmp-shell already and I was really hoping to avoid re-architecting the parser where I just wanted to add a few "hit and run" improvements.

This patch could be dropped if it poses a serious problem; mostly I wanted to ensure that QMP commands could be entered as they are displayed (with single quotes!)

I'll look briefly to see if there's a quick win-button for converting the quotes in a "safe" way.

--js

author
#9 lore raw

Re: [Qemu-devel] [PATCH 3/5] scripts: qmp-shell: allow single-quotes in JSON expressions

In reply to Eric Blake
Quoted 24 lines · Eric Blake
Quoted 14 lines · John Snow

As a convenience for the user, replace any single quotes given with double quotes so that the data will deserialize correctly via json.loads().

---
  scripts/qmp/qmp-shell | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)
scripts/qmp/qmp-shell +0 -0
index 5347f89..d7cb33d 100755

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 132

Quoted 5 lines
scripts/qmp/qmp-shell +1 -1
@@ -129,7 +129,7 @@ class QMPShell(qmp.QEMUMonitorProtocol):
 
              < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
          """
-        cmdargs = cmdline.split()
+        cmdargs = cmdline.replace("'", '"').split()

This replaces ALL single quotes, even if they would otherwise be escaped. That is, if I pass foo="a\'\"b", it will probably be corrupted.

Quoted 8 lines

qmp-shell exists mainly as a convenience for testing, and I doubt testers are likely to want to use unbalanced quotes as contents of strings, so I can give a weak:

But it's still worth thinking about whether there is a more robust solution to be used.

I done looked it up:

I can probably squash patches 2&3 with a single fix to use the ast.literal_eval() function, which does accept single or double quotes, since it parses python syntax instead of JSON.

I could probably also just have it 'try' each method until it finds something it likes.

--js

#10 lore raw

Re: [Qemu-devel] [PATCH 3/5] scripts: qmp-shell: allow single-quotes in JSON expressions

Quoted 9 lines · John Snow
Quoted 3 lines

But it's still worth thinking about whether there is a more robust solution to be used.

I done looked it up:

I can probably squash patches 2&3 with a single fix to use the ast.literal_eval() function, which does accept single or double quotes, since it parses python syntax instead of JSON.

Our QMP parser accepts 'string' as an extension to JSON (which is "string" only). That's about the only JSON extension our parser has at the moment.

Another thing to consider - JSON uses "bool-key":false while Python uses "bool-key":False (we don't yet support any QMP commands that would validly require a null argument, but JSON would use "key":null while Python uses "key":None). Once you get past that difference in spelling for True, False, and None (which could be done by string replace of bare 'true', 'false', and 'null'), then JSON is pretty much a strict subset of valid Python. Having qmp-shell be friendly and accept both JSON and Python spellings of the three keywords might actually be a nice hack. Maybe we should even extend our QMP parser to also accept True in place of true (output would still always be strict JSON, but there's no harm in making the input engine parse extensions, as we've already proven with the 'string' extension).

Quoted 3 lines

I could probably also just have it 'try' each method until it finds something it likes.

I haven't used qmp-shell much myself, so at this point, I'll leave it up to you how much effort you want to put into it.

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#11 lore raw

PATCH 4/5 scripts: qmp-shell: add transaction subshell

In reply to John Snow

Add a special processing mode to craft transactions.

By entering "transaction(" the shell will enter a special mode where each subsequent command will be saved as a transaction instead of executed as an individual command.

The transaction can be submitted by entering ")" on a line by itself.

Examples:

Separate lines:

(QEMU) transaction(
TRANS> block-dirty-bitmap-add node=drive0 name=bitmap1 TRANS> block-dirty-bitmap-clear node=drive0 name=bitmap0 TRANS> )

With a transaction action included on the first line:

(QEMU) transaction( block-dirty-bitmap-add node=drive0 name=bitmap2 TRANS> block-dirty-bitmap-add node=drive0 name=bitmap3 TRANS> )

As a one-liner, with just one transation action:

(QEMU) transaction( block-dirty-bitmap-add node=drive0 name=bitmap0 )

---
 scripts/qmp/qmp-shell | 43 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)
scripts/qmp/qmp-shell +42 -1
index d7cb33d..21d8f71 100755
@@ -59,6 +59,8 @@ class QMPShell(qmp.QEMUMonitorProtocol):
         self._greeting = None
         self._completer = None
         self._pp = pp
+        self._transmode = False
+        self._actions = list()
 
     def __get_address(self, arg):
         """
@@ -130,6 +132,37 @@ class QMPShell(qmp.QEMUMonitorProtocol):
             < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
         """
         cmdargs = cmdline.replace("'", '"').split()
+
+        # Transactional CLI entry/exit:
+        if cmdargs[0] == 'transaction(':
+            self._transmode = True
+            cmdargs.pop(0)
+        elif cmdargs[0] == ')' and self._transmode:
+            self._transmode = False
+            cmdargs.pop(0)
+            if cmdargs:
+                raise QMPShellError("Unexpected input after close of Transaction sub-shell")
+            qmpcmd = { 'execute': 'transaction',
+                       'arguments': { 'actions': self._actions } }
+            self._actions = list()
+            return qmpcmd
+
+        # Nothing to process?
+        if not cmdargs:
+            return None
+
+        # Parse and then cache this Transactional Action
+        if self._transmode:
+            finalize = False
+            action = { 'type': cmdargs[0], 'data': {} }
+            if cmdargs[-1] == ')':
+                cmdargs.pop(-1)
+                finalize = True
+            self.__cli_expr(cmdargs[1:], action['data'])
+            self._actions.append(action)
+            return self.__build_cmd(')') if finalize else None
+
+        # Standard command: parse and return it to be executed.
         qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
         self.__cli_expr(cmdargs[1:], qmpcmd['arguments'])
         return qmpcmd
@@ -142,6 +175,9 @@ class QMPShell(qmp.QEMUMonitorProtocol):
             print 'command format: <command-name> ',
             print '[arg-name1=arg1] ... [arg-nameN=argN]'
             return True
+        # For transaction mode, we may have just cached the action:
+        if qmpcmd is None:
+            return True
         resp = self.cmd_obj(qmpcmd)
         if resp is None:
             print 'Disconnected'
@@ -162,6 +198,11 @@ class QMPShell(qmp.QEMUMonitorProtocol):
         version = self._greeting['QMP']['version']['qemu']
         print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro'])
 
+    def get_prompt(self):
+        if self._transmode:
+            return "TRANS> "
+        return "(QEMU) "
+
     def read_exec_command(self, prompt):
         """
         Read and execute a command.
@@ -301,7 +342,7 @@ def main():
         die('Could not connect to %s' % addr)
 
     qemu.show_banner()
-    while qemu.read_exec_command('(QEMU) '):
+    while qemu.read_exec_command(qemu.get_prompt()):
         pass
     qemu.close()
 
Signature · 1 line
2.1.0
Reviewed-by on v1 4/5
#12 lore raw

Re: [Qemu-devel] [PATCH 4/5] scripts: qmp-shell: add transaction subshell

Quoted 17 lines · John Snow

Add a special processing mode to craft transactions.

By entering "transaction(" the shell will enter a special mode where each subsequent command will be saved as a transaction instead of executed as an individual command.

The transaction can be submitted by entering ")" on a line by itself.

Examples:

Separate lines:

(QEMU) transaction(
TRANS> block-dirty-bitmap-add node=drive0 name=bitmap1 TRANS> block-dirty-bitmap-clear node=drive0 name=bitmap0 TRANS> )

Quoted 7 lines

With a transaction action included on the first line:

(QEMU) transaction( block-dirty-bitmap-add node=drive0 name=bitmap2 TRANS> block-dirty-bitmap-add node=drive0 name=bitmap3 TRANS> )

As a one-liner, with just one transation action:

s/transation/transaction/

Quoted 7 lines

(QEMU) transaction( block-dirty-bitmap-add node=drive0 name=bitmap0 )

---
 scripts/qmp/qmp-shell | 43 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)

Nice. And very similar to the code reuse of how I added transaction support in libvirt (reusing the guts for constructing each action as a sibling of 'type', then stringing those together to an arguments array as a sibling of 'execute').

Minor question:

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 153

Quoted 5 lines
scripts/qmp/qmp-shell +5 -0
+
+        # Nothing to process?
+        if not cmdargs:
+            return None
+

This returns None if we have a blank line, whether or not we are in transaction mode...

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 163

Quoted 10 lines
scripts/qmp/qmp-shell +10 -0
+        # Parse and then cache this Transactional Action
+        if self._transmode:
+            finalize = False
+            action = { 'type': cmdargs[0], 'data': {} }
+            if cmdargs[-1] == ')':
+                cmdargs.pop(-1)
+                finalize = True
+            self.__cli_expr(cmdargs[1:], action['data'])
+            self._actions.append(action)
+            return self.__build_cmd(')') if finalize else None

and this returns None if we have a non-blank line but remain in transaction mode...

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 180

Quoted 7 lines
scripts/qmp/qmp-shell +3 -0
@@ -142,6 +175,9 @@ class QMPShell(qmp.QEMUMonitorProtocol):
             print 'command format: <command-name> ',
             print '[arg-name1=arg1] ... [arg-nameN=argN]'
             return True
+        # For transaction mode, we may have just cached the action:
+        if qmpcmd is None:
+            return True

...do we care if the user is entering blank lines even when not in transaction mode? That is, should this check be tightened to

if qmpcmd is None and self._transmode:

On the other hand, I don't know what the previous behavior was for blank lines (if an error message was printed or not), and I also think it's just fine to be silent on a blank line (and save error messages for non-blank lines that we can't parse). So I don't think it needs changing, so much as me trying to figure out what's going on.

Therefore, I'm fine with:

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#13 lore raw

Re: [Qemu-devel] [PATCH 4/5] scripts: qmp-shell: add transaction subshell

Quoted 43 lines · Eric Blake
Quoted 17 lines · John Snow

Add a special processing mode to craft transactions.

By entering "transaction(" the shell will enter a special mode where each subsequent command will be saved as a transaction instead of executed as an individual command.

The transaction can be submitted by entering ")" on a line by itself.

Examples:

Separate lines:

(QEMU) transaction(
TRANS> block-dirty-bitmap-add node=drive0 name=bitmap1 TRANS> block-dirty-bitmap-clear node=drive0 name=bitmap0 TRANS> )

Quoted 7 lines

With a transaction action included on the first line:

(QEMU) transaction( block-dirty-bitmap-add node=drive0 name=bitmap2 TRANS> block-dirty-bitmap-add node=drive0 name=bitmap3 TRANS> )

As a one-liner, with just one transation action:

s/transation/transaction/

Quoted 7 lines

(QEMU) transaction( block-dirty-bitmap-add node=drive0 name=bitmap0 )

---
  scripts/qmp/qmp-shell | 43 ++++++++++++++++++++++++++++++++++++++++++-
  1 file changed, 42 insertions(+), 1 deletion(-)

Nice. And very similar to the code reuse of how I added transaction support in libvirt (reusing the guts for constructing each action as a sibling of 'type', then stringing those together to an arguments array as a sibling of 'execute').

Minor question:

Quoted 9 lines

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 153

Quoted 5 lines
scripts/qmp/qmp-shell +5 -0
+
+        # Nothing to process?
+        if not cmdargs:
+            return None
+

This returns None if we have a blank line, whether or not we are in transaction mode...

Yeah, not a big deal. The function that invokes this one actually explicitly checks for an empty string and avoids __build_cmd already.

If it gets spaces, It actually currently errors out with "list index out of range" which is not helpful or interesting.

This will at least improve it to do "nothing."

Quoted 11 lines

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 163

Quoted 10 lines
scripts/qmp/qmp-shell +10 -0
+        # Parse and then cache this Transactional Action
+        if self._transmode:
+            finalize = False
+            action = { 'type': cmdargs[0], 'data': {} }
+            if cmdargs[-1] == ')':
+                cmdargs.pop(-1)
+                finalize = True
+            self.__cli_expr(cmdargs[1:], action['data'])
+            self._actions.append(action)
+            return self.__build_cmd(')') if finalize else None
Quoted 3 lines

and this returns None if we have a non-blank line but remain in transaction mode...

Yup, so the caller doesn't expect an object it needs to send right away.

Quoted 11 lines

scripts/qmp/qmp-shell class QMPShell(qmp.QEMUMonitorProtocol): · line 180

Quoted 7 lines
scripts/qmp/qmp-shell +3 -0
@@ -142,6 +175,9 @@ class QMPShell(qmp.QEMUMonitorProtocol):
              print 'command format: <command-name> ',
              print '[arg-name1=arg1] ... [arg-nameN=argN]'
              return True
+        # For transaction mode, we may have just cached the action:
+        if qmpcmd is None:
+            return True

...do we care if the user is entering blank lines even when not in transaction mode? That is, should this check be tightened to

The behavior of just ignoring empty and blank lines is suitable regardless of mode, I think, unless you have a counter argument.

For reference, a "False" return here will end the shell. It's seen as non-recoverable.

Quoted 12 lines

if qmpcmd is None and self._transmode:

On the other hand, I don't know what the previous behavior was for blank lines (if an error message was printed or not), and I also think it's just fine to be silent on a blank line (and save error messages for non-blank lines that we can't parse). So I don't think it needs changing, so much as me trying to figure out what's going on.

Therefore, I'm fine with:

#14 lore raw

Re: [Qemu-devel] [PATCH 4/5] scripts: qmp-shell: add transaction subshell

Quoted 11 lines · John Snow

Yeah, not a big deal. The function that invokes this one actually explicitly checks for an empty string and avoids __build_cmd already.

If it gets spaces, It actually currently errors out with "list index out of range" which is not helpful or interesting.

This will at least improve it to do "nothing."

The behavior of just ignoring empty and blank lines is suitable regardless of mode, I think, unless you have a counter argument.

No counter argument here; but it does mean that we should improve the commit message, to mention that it is an intentionally nice side effect of the change :)

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#15 lore raw

PATCH 5/5 scripts: qmp-shell: Add verbose flag

In reply to John Snow

Add a verbose flag that shows the QMP command that was constructed, to allow for later copy/pasting, reference, debugging, etc.

---
 scripts/qmp/qmp-shell | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)
scripts/qmp/qmp-shell +16 -5
index 21d8f71..72473fc 100755
@@ -167,6 +167,12 @@ class QMPShell(qmp.QEMUMonitorProtocol):
         self.__cli_expr(cmdargs[1:], qmpcmd['arguments'])
         return qmpcmd
 
+    def _print(self, qmp):
+        if self._pp is not None:
+            self._pp.pprint(qmp)
+        else:
+            print qmp
+
     def _execute_cmd(self, cmdline):
         try:
             qmpcmd = self.__build_cmd(cmdline)
@@ -178,15 +184,13 @@ class QMPShell(qmp.QEMUMonitorProtocol):
         # For transaction mode, we may have just cached the action:
         if qmpcmd is None:
             return True
+        if self._verbose:
+            self._print(qmpcmd)
         resp = self.cmd_obj(qmpcmd)
         if resp is None:
             print 'Disconnected'
             return False
-
-        if self._pp is not None:
-            self._pp.pprint(resp)
-        else:
-            print resp
+        self._print(resp)
         return True
 
     def connect(self):
@@ -222,6 +226,9 @@ class QMPShell(qmp.QEMUMonitorProtocol):
         else:
             return self._execute_cmd(cmdline)
 
+    def set_verbosity(self, verbose):
+        self._verbose = verbose
+
 class HMPShell(QMPShell):
     def __init__(self, address):
         QMPShell.__init__(self, address)
@@ -307,6 +314,7 @@ def main():
     qemu = None
     hmp = False
     pp = None
+    verbose = False
 
     try:
         for arg in sys.argv[1:]:
@@ -318,6 +326,8 @@ def main():
                 if pp is not None:
                     fail_cmdline(arg)
                 pp = pprint.PrettyPrinter(indent=4)
+            elif arg == "-v":
+                verbose = True
             else:
                 if qemu is not None:
                     fail_cmdline(arg)
@@ -342,6 +352,7 @@ def main():
         die('Could not connect to %s' % addr)
 
     qemu.show_banner()
+    qemu.set_verbosity(verbose)
     while qemu.read_exec_command(qemu.get_prompt()):
         pass
     qemu.close()
Signature · 1 line
2.1.0
Reviewed-by on v1 5/5
#16 lore raw

Re: [Qemu-devel] [PATCH 5/5] scripts: qmp-shell: Add verbose flag

Quoted 3 lines · John Snow

Add a verbose flag that shows the QMP command that was constructed, to allow for later copy/pasting, reference, debugging, etc.

Very useful.

Quoted 6 lines
---
 scripts/qmp/qmp-shell | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)
Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org