Patchwork qmp: add test tool for QMP

login
register
mail settings
Submitter Anthony Liguori
Date Nov. 7, 2011, 3:11 p.m.
Message ID <1320678675-21379-1-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/124095/
State New
Headers show

Comments

Anthony Liguori - Nov. 7, 2011, 3:11 p.m.
I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
important differences:

1) It is not interactive.  That makes it useful for scripting.

2) qmp-shell:

(QEMU) set_password protocol=vnc password=foo

3) qmp:

$ qmp set_password --protocol=vnc --password=foo

4) Extensible, git-style interface.  If an invalid command name is passed, it
   will try to exec qmp-$1.

5) It attempts to pretty print the JSON responses in a shell friendly format
   such that tools can work with the output.

Hope others will also find it useful.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 QMP/qmp |  120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 120 insertions(+), 0 deletions(-)
 create mode 100755 QMP/qmp
Luiz Capitulino - Nov. 7, 2011, 4:08 p.m.
On Mon,  7 Nov 2011 09:11:15 -0600
Anthony Liguori <aliguori@us.ibm.com> wrote:

> I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
> important differences:
> 
> 1) It is not interactive.  That makes it useful for scripting.
> 
> 2) qmp-shell:
> 
> (QEMU) set_password protocol=vnc password=foo
> 
> 3) qmp:
> 
> $ qmp set_password --protocol=vnc --password=foo
> 
> 4) Extensible, git-style interface.  If an invalid command name is passed, it
>    will try to exec qmp-$1.
> 
> 5) It attempts to pretty print the JSON responses in a shell friendly format
>    such that tools can work with the output.
> 
> Hope others will also find it useful.
> 
> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>

Acked-by: Luiz Capitulino <lcapitulino@redhat.com>

> ---
>  QMP/qmp |  120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 files changed, 120 insertions(+), 0 deletions(-)
>  create mode 100755 QMP/qmp
> 
> diff --git a/QMP/qmp b/QMP/qmp
> new file mode 100755
> index 0000000..7b2a3c7
> --- /dev/null
> +++ b/QMP/qmp
> @@ -0,0 +1,120 @@
> +#!/usr/bin/python
> +#
> +# QMP command line tool
> +#
> +# Copyright IBM, Corp. 2011
> +#
> +# Authors:
> +#  Anthony Liguori <aliguori@us.ibm.com>
> +#
> +# This work is licensed under the terms of the GNU GPLv2 or later.
> +# See the COPYING file in the top-level directory.
> +
> +import sys, os
> +from qmp import QEMUMonitorProtocol
> +
> +def print_response(rsp, prefix=[]):
> +    if type(rsp) == list:
> +        i = 0
> +        for item in rsp:
> +            if prefix == []:
> +                prefix = ['item']
> +            print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)])
> +            i += 1
> +    elif type(rsp) == dict:
> +        for key in rsp.keys():
> +            print_response(rsp[key], prefix + [key])
> +    else:
> +        if len(prefix):
> +            print '%s: %s' % ('.'.join(prefix), rsp)
> +        else:
> +            print '%s' % (rsp)
> +
> +def main(args):
> +    path = None
> +
> +    # Use QMP_PATH if it's set
> +    if os.environ.has_key('QMP_PATH'):
> +        path = os.environ['QMP_PATH']
> +
> +    while len(args):
> +        arg = args[0]
> +
> +        if arg.startswith('--'):
> +            arg = arg[2:]
> +            if arg.find('=') == -1:
> +                value = True
> +            else:
> +                arg, value = arg.split('=', 1)
> +
> +            if arg in ['path']:
> +                path = value
> +            elif arg in ['help']:
> +                os.execlp('man', 'man', 'qmp')
> +            else:
> +                print 'Unknown argument "%s"' % arg
> +
> +            args = args[1:]
> +        else:
> +            break
> +
> +    if not path:
> +        print "QMP path isn't set, use --path or set QMP_PATH"
> +        return 1
> +
> +    command, args = args[0], args[1:]
> +
> +    if command in ['help']:
> +        os.execlp('man', 'man', 'qmp')
> +
> +    srv = QEMUMonitorProtocol(path)
> +    srv.connect()
> +
> +    def do_command(srv, cmd, **kwds):
> +        rsp = srv.cmd(cmd, kwds)
> +        if rsp.has_key('error'):
> +            raise Exception(rsp['error']['desc'])
> +        return rsp['return']
> +
> +    commands = map(lambda x: x['name'], do_command(srv, 'query-commands'))
> +
> +    srv.close()
> +
> +    if command not in commands:
> +        fullcmd = 'qmp-%s' % command
> +        try:
> +            os.environ['QMP_PATH'] = path
> +            os.execvp(fullcmd, [fullcmd] + args)
> +        except OSError, (errno, msg):
> +            if errno == 2:
> +                print 'Command "%s" not found.' % (fullcmd)
> +                return 1
> +            raise
> +        return 0
> +
> +    srv = QEMUMonitorProtocol(path)
> +    srv.connect()
> +                
> +    arguments = {}
> +    for arg in args:
> +        if not arg.startswith('--'):
> +            print 'Unknown argument "%s"' % arg
> +            return 1
> +
> +        arg = arg[2:]
> +        if arg.find('=') == -1:
> +            value = True
> +        else:
> +            arg, value = arg.split('=', 1)
> +
> +        if arg in ['help']:
> +            os.execlp('man', 'man', 'qmp-%s' % command)
> +            return 1
> +
> +        arguments[arg] = value
> +
> +    rsp = do_command(srv, command, **arguments)
> +    print_response(rsp)
> +
> +if __name__ == '__main__':
> +    sys.exit(main(sys.argv[1:]))
Anthony Liguori - Nov. 7, 2011, 4:09 p.m.
On 11/07/2011 10:08 AM, Luiz Capitulino wrote:
> On Mon,  7 Nov 2011 09:11:15 -0600
> Anthony Liguori<aliguori@us.ibm.com>  wrote:
>
>> I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
>> important differences:
>>
>> 1) It is not interactive.  That makes it useful for scripting.
>>
>> 2) qmp-shell:
>>
>> (QEMU) set_password protocol=vnc password=foo
>>
>> 3) qmp:
>>
>> $ qmp set_password --protocol=vnc --password=foo
>>
>> 4) Extensible, git-style interface.  If an invalid command name is passed, it
>>     will try to exec qmp-$1.
>>
>> 5) It attempts to pretty print the JSON responses in a shell friendly format
>>     such that tools can work with the output.
>>
>> Hope others will also find it useful.
>>
>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>
> Acked-by: Luiz Capitulino<lcapitulino@redhat.com>

BTW, one thing I'd like to try at some point soon is to generate man pages from 
qapi-schema.json.  If you notice in the script, it does online help by invoking man.

Regards,

Anthony Liguori

>
>> ---
>>   QMP/qmp |  120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   1 files changed, 120 insertions(+), 0 deletions(-)
>>   create mode 100755 QMP/qmp
>>
>> diff --git a/QMP/qmp b/QMP/qmp
>> new file mode 100755
>> index 0000000..7b2a3c7
>> --- /dev/null
>> +++ b/QMP/qmp
>> @@ -0,0 +1,120 @@
>> +#!/usr/bin/python
>> +#
>> +# QMP command line tool
>> +#
>> +# Copyright IBM, Corp. 2011
>> +#
>> +# Authors:
>> +#  Anthony Liguori<aliguori@us.ibm.com>
>> +#
>> +# This work is licensed under the terms of the GNU GPLv2 or later.
>> +# See the COPYING file in the top-level directory.
>> +
>> +import sys, os
>> +from qmp import QEMUMonitorProtocol
>> +
>> +def print_response(rsp, prefix=[]):
>> +    if type(rsp) == list:
>> +        i = 0
>> +        for item in rsp:
>> +            if prefix == []:
>> +                prefix = ['item']
>> +            print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)])
>> +            i += 1
>> +    elif type(rsp) == dict:
>> +        for key in rsp.keys():
>> +            print_response(rsp[key], prefix + [key])
>> +    else:
>> +        if len(prefix):
>> +            print '%s: %s' % ('.'.join(prefix), rsp)
>> +        else:
>> +            print '%s' % (rsp)
>> +
>> +def main(args):
>> +    path = None
>> +
>> +    # Use QMP_PATH if it's set
>> +    if os.environ.has_key('QMP_PATH'):
>> +        path = os.environ['QMP_PATH']
>> +
>> +    while len(args):
>> +        arg = args[0]
>> +
>> +        if arg.startswith('--'):
>> +            arg = arg[2:]
>> +            if arg.find('=') == -1:
>> +                value = True
>> +            else:
>> +                arg, value = arg.split('=', 1)
>> +
>> +            if arg in ['path']:
>> +                path = value
>> +            elif arg in ['help']:
>> +                os.execlp('man', 'man', 'qmp')
>> +            else:
>> +                print 'Unknown argument "%s"' % arg
>> +
>> +            args = args[1:]
>> +        else:
>> +            break
>> +
>> +    if not path:
>> +        print "QMP path isn't set, use --path or set QMP_PATH"
>> +        return 1
>> +
>> +    command, args = args[0], args[1:]
>> +
>> +    if command in ['help']:
>> +        os.execlp('man', 'man', 'qmp')
>> +
>> +    srv = QEMUMonitorProtocol(path)
>> +    srv.connect()
>> +
>> +    def do_command(srv, cmd, **kwds):
>> +        rsp = srv.cmd(cmd, kwds)
>> +        if rsp.has_key('error'):
>> +            raise Exception(rsp['error']['desc'])
>> +        return rsp['return']
>> +
>> +    commands = map(lambda x: x['name'], do_command(srv, 'query-commands'))
>> +
>> +    srv.close()
>> +
>> +    if command not in commands:
>> +        fullcmd = 'qmp-%s' % command
>> +        try:
>> +            os.environ['QMP_PATH'] = path
>> +            os.execvp(fullcmd, [fullcmd] + args)
>> +        except OSError, (errno, msg):
>> +            if errno == 2:
>> +                print 'Command "%s" not found.' % (fullcmd)
>> +                return 1
>> +            raise
>> +        return 0
>> +
>> +    srv = QEMUMonitorProtocol(path)
>> +    srv.connect()
>> +
>> +    arguments = {}
>> +    for arg in args:
>> +        if not arg.startswith('--'):
>> +            print 'Unknown argument "%s"' % arg
>> +            return 1
>> +
>> +        arg = arg[2:]
>> +        if arg.find('=') == -1:
>> +            value = True
>> +        else:
>> +            arg, value = arg.split('=', 1)
>> +
>> +        if arg in ['help']:
>> +            os.execlp('man', 'man', 'qmp-%s' % command)
>> +            return 1
>> +
>> +        arguments[arg] = value
>> +
>> +    rsp = do_command(srv, command, **arguments)
>> +    print_response(rsp)
>> +
>> +if __name__ == '__main__':
>> +    sys.exit(main(sys.argv[1:]))
>
>
Luiz Capitulino - Nov. 7, 2011, 4:30 p.m.
On Mon, 07 Nov 2011 10:09:55 -0600
Anthony Liguori <anthony@codemonkey.ws> wrote:

> On 11/07/2011 10:08 AM, Luiz Capitulino wrote:
> > On Mon,  7 Nov 2011 09:11:15 -0600
> > Anthony Liguori<aliguori@us.ibm.com>  wrote:
> >
> >> I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
> >> important differences:
> >>
> >> 1) It is not interactive.  That makes it useful for scripting.
> >>
> >> 2) qmp-shell:
> >>
> >> (QEMU) set_password protocol=vnc password=foo
> >>
> >> 3) qmp:
> >>
> >> $ qmp set_password --protocol=vnc --password=foo
> >>
> >> 4) Extensible, git-style interface.  If an invalid command name is passed, it
> >>     will try to exec qmp-$1.
> >>
> >> 5) It attempts to pretty print the JSON responses in a shell friendly format
> >>     such that tools can work with the output.
> >>
> >> Hope others will also find it useful.
> >>
> >> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
> >
> > Acked-by: Luiz Capitulino<lcapitulino@redhat.com>
> 
> BTW, one thing I'd like to try at some point soon is to generate man pages from 
> qapi-schema.json.  If you notice in the script, it does online help by invoking man.

Yes, I did notice it. I didn't comment on it because I imagined you had plans
about it.

PS: I don't think this needs to go through my tree.

> 
> Regards,
> 
> Anthony Liguori
> 
> >
> >> ---
> >>   QMP/qmp |  120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> >>   1 files changed, 120 insertions(+), 0 deletions(-)
> >>   create mode 100755 QMP/qmp
> >>
> >> diff --git a/QMP/qmp b/QMP/qmp
> >> new file mode 100755
> >> index 0000000..7b2a3c7
> >> --- /dev/null
> >> +++ b/QMP/qmp
> >> @@ -0,0 +1,120 @@
> >> +#!/usr/bin/python
> >> +#
> >> +# QMP command line tool
> >> +#
> >> +# Copyright IBM, Corp. 2011
> >> +#
> >> +# Authors:
> >> +#  Anthony Liguori<aliguori@us.ibm.com>
> >> +#
> >> +# This work is licensed under the terms of the GNU GPLv2 or later.
> >> +# See the COPYING file in the top-level directory.
> >> +
> >> +import sys, os
> >> +from qmp import QEMUMonitorProtocol
> >> +
> >> +def print_response(rsp, prefix=[]):
> >> +    if type(rsp) == list:
> >> +        i = 0
> >> +        for item in rsp:
> >> +            if prefix == []:
> >> +                prefix = ['item']
> >> +            print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)])
> >> +            i += 1
> >> +    elif type(rsp) == dict:
> >> +        for key in rsp.keys():
> >> +            print_response(rsp[key], prefix + [key])
> >> +    else:
> >> +        if len(prefix):
> >> +            print '%s: %s' % ('.'.join(prefix), rsp)
> >> +        else:
> >> +            print '%s' % (rsp)
> >> +
> >> +def main(args):
> >> +    path = None
> >> +
> >> +    # Use QMP_PATH if it's set
> >> +    if os.environ.has_key('QMP_PATH'):
> >> +        path = os.environ['QMP_PATH']
> >> +
> >> +    while len(args):
> >> +        arg = args[0]
> >> +
> >> +        if arg.startswith('--'):
> >> +            arg = arg[2:]
> >> +            if arg.find('=') == -1:
> >> +                value = True
> >> +            else:
> >> +                arg, value = arg.split('=', 1)
> >> +
> >> +            if arg in ['path']:
> >> +                path = value
> >> +            elif arg in ['help']:
> >> +                os.execlp('man', 'man', 'qmp')
> >> +            else:
> >> +                print 'Unknown argument "%s"' % arg
> >> +
> >> +            args = args[1:]
> >> +        else:
> >> +            break
> >> +
> >> +    if not path:
> >> +        print "QMP path isn't set, use --path or set QMP_PATH"
> >> +        return 1
> >> +
> >> +    command, args = args[0], args[1:]
> >> +
> >> +    if command in ['help']:
> >> +        os.execlp('man', 'man', 'qmp')
> >> +
> >> +    srv = QEMUMonitorProtocol(path)
> >> +    srv.connect()
> >> +
> >> +    def do_command(srv, cmd, **kwds):
> >> +        rsp = srv.cmd(cmd, kwds)
> >> +        if rsp.has_key('error'):
> >> +            raise Exception(rsp['error']['desc'])
> >> +        return rsp['return']
> >> +
> >> +    commands = map(lambda x: x['name'], do_command(srv, 'query-commands'))
> >> +
> >> +    srv.close()
> >> +
> >> +    if command not in commands:
> >> +        fullcmd = 'qmp-%s' % command
> >> +        try:
> >> +            os.environ['QMP_PATH'] = path
> >> +            os.execvp(fullcmd, [fullcmd] + args)
> >> +        except OSError, (errno, msg):
> >> +            if errno == 2:
> >> +                print 'Command "%s" not found.' % (fullcmd)
> >> +                return 1
> >> +            raise
> >> +        return 0
> >> +
> >> +    srv = QEMUMonitorProtocol(path)
> >> +    srv.connect()
> >> +
> >> +    arguments = {}
> >> +    for arg in args:
> >> +        if not arg.startswith('--'):
> >> +            print 'Unknown argument "%s"' % arg
> >> +            return 1
> >> +
> >> +        arg = arg[2:]
> >> +        if arg.find('=') == -1:
> >> +            value = True
> >> +        else:
> >> +            arg, value = arg.split('=', 1)
> >> +
> >> +        if arg in ['help']:
> >> +            os.execlp('man', 'man', 'qmp-%s' % command)
> >> +            return 1
> >> +
> >> +        arguments[arg] = value
> >> +
> >> +    rsp = do_command(srv, command, **arguments)
> >> +    print_response(rsp)
> >> +
> >> +if __name__ == '__main__':
> >> +    sys.exit(main(sys.argv[1:]))
> >
> >
>
Anthony Liguori - Nov. 7, 2011, 4:35 p.m.
On 11/07/2011 10:30 AM, Luiz Capitulino wrote:
> On Mon, 07 Nov 2011 10:09:55 -0600
> Anthony Liguori<anthony@codemonkey.ws>  wrote:
>
>> On 11/07/2011 10:08 AM, Luiz Capitulino wrote:
>>> On Mon,  7 Nov 2011 09:11:15 -0600
>>> Anthony Liguori<aliguori@us.ibm.com>   wrote:
>>>
>>>> I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
>>>> important differences:
>>>>
>>>> 1) It is not interactive.  That makes it useful for scripting.
>>>>
>>>> 2) qmp-shell:
>>>>
>>>> (QEMU) set_password protocol=vnc password=foo
>>>>
>>>> 3) qmp:
>>>>
>>>> $ qmp set_password --protocol=vnc --password=foo
>>>>
>>>> 4) Extensible, git-style interface.  If an invalid command name is passed, it
>>>>      will try to exec qmp-$1.
>>>>
>>>> 5) It attempts to pretty print the JSON responses in a shell friendly format
>>>>      such that tools can work with the output.
>>>>
>>>> Hope others will also find it useful.
>>>>
>>>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>>>
>>> Acked-by: Luiz Capitulino<lcapitulino@redhat.com>
>>
>> BTW, one thing I'd like to try at some point soon is to generate man pages from
>> qapi-schema.json.  If you notice in the script, it does online help by invoking man.
>
> Yes, I did notice it. I didn't comment on it because I imagined you had plans
> about it.
>
> PS: I don't think this needs to go through my tree.

What do you want to do with qmp.py?  Do you feel comfortable installing it in 
$PYTHONPATH and treating it as a supported API?

Regards,

Anthony Liguori
Luiz Capitulino - Nov. 7, 2011, 4:39 p.m.
On Mon, 07 Nov 2011 10:35:51 -0600
Anthony Liguori <anthony@codemonkey.ws> wrote:

> On 11/07/2011 10:30 AM, Luiz Capitulino wrote:
> > On Mon, 07 Nov 2011 10:09:55 -0600
> > Anthony Liguori<anthony@codemonkey.ws>  wrote:
> >
> >> On 11/07/2011 10:08 AM, Luiz Capitulino wrote:
> >>> On Mon,  7 Nov 2011 09:11:15 -0600
> >>> Anthony Liguori<aliguori@us.ibm.com>   wrote:
> >>>
> >>>> I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
> >>>> important differences:
> >>>>
> >>>> 1) It is not interactive.  That makes it useful for scripting.
> >>>>
> >>>> 2) qmp-shell:
> >>>>
> >>>> (QEMU) set_password protocol=vnc password=foo
> >>>>
> >>>> 3) qmp:
> >>>>
> >>>> $ qmp set_password --protocol=vnc --password=foo
> >>>>
> >>>> 4) Extensible, git-style interface.  If an invalid command name is passed, it
> >>>>      will try to exec qmp-$1.
> >>>>
> >>>> 5) It attempts to pretty print the JSON responses in a shell friendly format
> >>>>      such that tools can work with the output.
> >>>>
> >>>> Hope others will also find it useful.
> >>>>
> >>>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
> >>>
> >>> Acked-by: Luiz Capitulino<lcapitulino@redhat.com>
> >>
> >> BTW, one thing I'd like to try at some point soon is to generate man pages from
> >> qapi-schema.json.  If you notice in the script, it does online help by invoking man.
> >
> > Yes, I did notice it. I didn't comment on it because I imagined you had plans
> > about it.
> >
> > PS: I don't think this needs to go through my tree.
> 
> What do you want to do with qmp.py?  Do you feel comfortable installing it in 
> $PYTHONPATH and treating it as a supported API?

I probably don't. I coded it as demo in the very beginning of QMP, maybe
we should first define what we expect from a QMP Python class then we
can see whether it fits or not... I feel it needs to be revamped.
Alon Levy - Nov. 7, 2011, 5:26 p.m.
On Mon, Nov 07, 2011 at 02:39:29PM -0200, Luiz Capitulino wrote:
> On Mon, 07 Nov 2011 10:35:51 -0600
> Anthony Liguori <anthony@codemonkey.ws> wrote:
> 
> > On 11/07/2011 10:30 AM, Luiz Capitulino wrote:
> > > On Mon, 07 Nov 2011 10:09:55 -0600
> > > Anthony Liguori<anthony@codemonkey.ws>  wrote:
> > >
> > >> On 11/07/2011 10:08 AM, Luiz Capitulino wrote:
> > >>> On Mon,  7 Nov 2011 09:11:15 -0600
> > >>> Anthony Liguori<aliguori@us.ibm.com>   wrote:
> > >>>
> > >>>> I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
> > >>>> important differences:
> > >>>>
> > >>>> 1) It is not interactive.  That makes it useful for scripting.
> > >>>>
> > >>>> 2) qmp-shell:
> > >>>>
> > >>>> (QEMU) set_password protocol=vnc password=foo
> > >>>>
> > >>>> 3) qmp:
> > >>>>
> > >>>> $ qmp set_password --protocol=vnc --password=foo
> > >>>>
> > >>>> 4) Extensible, git-style interface.  If an invalid command name is passed, it
> > >>>>      will try to exec qmp-$1.
> > >>>>
> > >>>> 5) It attempts to pretty print the JSON responses in a shell friendly format
> > >>>>      such that tools can work with the output.
> > >>>>
> > >>>> Hope others will also find it useful.
> > >>>>
> > >>>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
> > >>>
> > >>> Acked-by: Luiz Capitulino<lcapitulino@redhat.com>
> > >>
> > >> BTW, one thing I'd like to try at some point soon is to generate man pages from
> > >> qapi-schema.json.  If you notice in the script, it does online help by invoking man.
> > >
> > > Yes, I did notice it. I didn't comment on it because I imagined you had plans
> > > about it.
> > >
> > > PS: I don't think this needs to go through my tree.
> > 
> > What do you want to do with qmp.py?  Do you feel comfortable installing it in 
> > $PYTHONPATH and treating it as a supported API?
> 
> I probably don't. I coded it as demo in the very beginning of QMP, maybe
> we should first define what we expect from a QMP Python class then we
> can see whether it fits or not... I feel it needs to be revamped.
> 

It should not blocking, i.e. for event notification. I have a patch that
fixes that but breaks tab-completion.
Mark Wu - Nov. 8, 2011, 8:36 a.m.
When I run this tool, I got two python exceptions. It turned out that 
both of them were caused by wrong usage.  Do you think we need add 
validation for input to handle these cases?

Thanks.

1. Not using '=' for path:
  $ ./QMP/qmp --path monitor-address
Traceback (most recent call last):
   File "./QMP/qmp", line 120, in <module>
     sys.exit(main(sys.argv[1:]))
   File "./QMP/qmp", line 71, in main
     srv.connect()
   File "/home/mark/work/source/qemu/QMP/qmp.py", line 85, in connect
     self.__sock.connect(self.__address)
   File "<string>", line 1, in connect
TypeError: argument must be string or read-only character buffer, not bool

Proposed patch:

@@ -48,7 +48,8 @@ def main(args):
                  arg, value = arg.split('=', 1)

              if arg in ['path']:
-                path = value
+               if isinstance(value, basestring):
+                    path = value


2. No qmp comand given in command line
$ ./QMP/qmp --path=monitor-address
Traceback (most recent call last):
   File "./QMP/qmp", line 120, in <module>
     sys.exit(main(sys.argv[1:]))
   File "./QMP/qmp", line 65, in main
     command, args = args[0], args[1:]
IndexError: list index out of range

@@ -62,11 +63,17 @@ def main(args):
          print "QMP path isn't set, use --path or set QMP_PATH"
          return 1

Proposed patch:
-    command, args = args[0], args[1:]
+    if len(args):
+       command, args = args[0], args[1:]
+    else:
+       print 'No command found'
+       print 'Usage: "qmp [--path=monitor-address] qmp-cmd arguments"'
+       return 1








On 11/07/2011 11:11 PM, Anthony Liguori wrote:
> I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
> important differences:
>
> 1) It is not interactive.  That makes it useful for scripting.
>
> 2) qmp-shell:
>
> (QEMU) set_password protocol=vnc password=foo
>
> 3) qmp:
>
> $ qmp set_password --protocol=vnc --password=foo
>
> 4) Extensible, git-style interface.  If an invalid command name is passed, it
>     will try to exec qmp-$1.
>
> 5) It attempts to pretty print the JSON responses in a shell friendly format
>     such that tools can work with the output.
>
> Hope others will also find it useful.
>
> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
> ---
>   QMP/qmp |  120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>   1 files changed, 120 insertions(+), 0 deletions(-)
>   create mode 100755 QMP/qmp
>
> diff --git a/QMP/qmp b/QMP/qmp
> new file mode 100755
> index 0000000..7b2a3c7
> --- /dev/null
> +++ b/QMP/qmp
> @@ -0,0 +1,120 @@
> +#!/usr/bin/python
> +#
> +# QMP command line tool
> +#
> +# Copyright IBM, Corp. 2011
> +#
> +# Authors:
> +#  Anthony Liguori<aliguori@us.ibm.com>
> +#
> +# This work is licensed under the terms of the GNU GPLv2 or later.
> +# See the COPYING file in the top-level directory.
> +
> +import sys, os
> +from qmp import QEMUMonitorProtocol
> +
> +def print_response(rsp, prefix=[]):
> +    if type(rsp) == list:
> +        i = 0
> +        for item in rsp:
> +            if prefix == []:
> +                prefix = ['item']
> +            print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)])
> +            i += 1
> +    elif type(rsp) == dict:
> +        for key in rsp.keys():
> +            print_response(rsp[key], prefix + [key])
> +    else:
> +        if len(prefix):
> +            print '%s: %s' % ('.'.join(prefix), rsp)
> +        else:
> +            print '%s' % (rsp)
> +
> +def main(args):
> +    path = None
> +
> +    # Use QMP_PATH if it's set
> +    if os.environ.has_key('QMP_PATH'):
> +        path = os.environ['QMP_PATH']
> +
> +    while len(args):
> +        arg = args[0]
> +
> +        if arg.startswith('--'):
> +            arg = arg[2:]
> +            if arg.find('=') == -1:
> +                value = True
> +            else:
> +                arg, value = arg.split('=', 1)
> +
> +            if arg in ['path']:
> +                path = value
> +            elif arg in ['help']:
> +                os.execlp('man', 'man', 'qmp')
> +            else:
> +                print 'Unknown argument "%s"' % arg
> +
> +            args = args[1:]
> +        else:
> +            break
> +
> +    if not path:
> +        print "QMP path isn't set, use --path or set QMP_PATH"
> +        return 1
> +
> +    command, args = args[0], args[1:]
> +
> +    if command in ['help']:
> +        os.execlp('man', 'man', 'qmp')
> +
> +    srv = QEMUMonitorProtocol(path)
> +    srv.connect()
> +
> +    def do_command(srv, cmd, **kwds):
> +        rsp = srv.cmd(cmd, kwds)
> +        if rsp.has_key('error'):
> +            raise Exception(rsp['error']['desc'])
> +        return rsp['return']
> +
> +    commands = map(lambda x: x['name'], do_command(srv, 'query-commands'))
> +
> +    srv.close()
> +
> +    if command not in commands:
> +        fullcmd = 'qmp-%s' % command
> +        try:
> +            os.environ['QMP_PATH'] = path
> +            os.execvp(fullcmd, [fullcmd] + args)
> +        except OSError, (errno, msg):
> +            if errno == 2:
> +                print 'Command "%s" not found.' % (fullcmd)
> +                return 1
> +            raise
> +        return 0
> +
> +    srv = QEMUMonitorProtocol(path)
> +    srv.connect()
> +
> +    arguments = {}
> +    for arg in args:
> +        if not arg.startswith('--'):
> +            print 'Unknown argument "%s"' % arg
> +            return 1
> +
> +        arg = arg[2:]
> +        if arg.find('=') == -1:
> +            value = True
> +        else:
> +            arg, value = arg.split('=', 1)
> +
> +        if arg in ['help']:
> +            os.execlp('man', 'man', 'qmp-%s' % command)
> +            return 1
> +
> +        arguments[arg] = value
> +
> +    rsp = do_command(srv, command, **arguments)
> +    print_response(rsp)
> +
> +if __name__ == '__main__':
> +    sys.exit(main(sys.argv[1:]))
Luiz Capitulino - Nov. 8, 2011, 11:56 a.m.
On Tue, 08 Nov 2011 16:36:55 +0800
Mark Wu <wudxw@linux.vnet.ibm.com> wrote:

> When I run this tool, I got two python exceptions. It turned out that 
> both of them were caused by wrong usage.  Do you think we need add 
> validation for input to handle these cases?

Yes.

> 
> Thanks.
> 
> 1. Not using '=' for path:
>   $ ./QMP/qmp --path monitor-address
> Traceback (most recent call last):
>    File "./QMP/qmp", line 120, in <module>
>      sys.exit(main(sys.argv[1:]))
>    File "./QMP/qmp", line 71, in main
>      srv.connect()
>    File "/home/mark/work/source/qemu/QMP/qmp.py", line 85, in connect
>      self.__sock.connect(self.__address)
>    File "<string>", line 1, in connect
> TypeError: argument must be string or read-only character buffer, not bool
> 
> Proposed patch:
> 
> @@ -48,7 +48,8 @@ def main(args):
>                   arg, value = arg.split('=', 1)
> 
>               if arg in ['path']:
> -                path = value
> +               if isinstance(value, basestring):
> +                    path = value
> 
> 
> 2. No qmp comand given in command line
> $ ./QMP/qmp --path=monitor-address
> Traceback (most recent call last):
>    File "./QMP/qmp", line 120, in <module>
>      sys.exit(main(sys.argv[1:]))
>    File "./QMP/qmp", line 65, in main
>      command, args = args[0], args[1:]
> IndexError: list index out of range
> 
> @@ -62,11 +63,17 @@ def main(args):
>           print "QMP path isn't set, use --path or set QMP_PATH"
>           return 1
> 
> Proposed patch:
> -    command, args = args[0], args[1:]
> +    if len(args):
> +       command, args = args[0], args[1:]
> +    else:
> +       print 'No command found'
> +       print 'Usage: "qmp [--path=monitor-address] qmp-cmd arguments"'
> +       return 1
> 
> 
> 
> 
> 
> 
> 
> 
> On 11/07/2011 11:11 PM, Anthony Liguori wrote:
> > I wrote this quickly to aid in testing.  It's similar to qmp-shell with a few
> > important differences:
> >
> > 1) It is not interactive.  That makes it useful for scripting.
> >
> > 2) qmp-shell:
> >
> > (QEMU) set_password protocol=vnc password=foo
> >
> > 3) qmp:
> >
> > $ qmp set_password --protocol=vnc --password=foo
> >
> > 4) Extensible, git-style interface.  If an invalid command name is passed, it
> >     will try to exec qmp-$1.
> >
> > 5) It attempts to pretty print the JSON responses in a shell friendly format
> >     such that tools can work with the output.
> >
> > Hope others will also find it useful.
> >
> > Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
> > ---
> >   QMP/qmp |  120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> >   1 files changed, 120 insertions(+), 0 deletions(-)
> >   create mode 100755 QMP/qmp
> >
> > diff --git a/QMP/qmp b/QMP/qmp
> > new file mode 100755
> > index 0000000..7b2a3c7
> > --- /dev/null
> > +++ b/QMP/qmp
> > @@ -0,0 +1,120 @@
> > +#!/usr/bin/python
> > +#
> > +# QMP command line tool
> > +#
> > +# Copyright IBM, Corp. 2011
> > +#
> > +# Authors:
> > +#  Anthony Liguori<aliguori@us.ibm.com>
> > +#
> > +# This work is licensed under the terms of the GNU GPLv2 or later.
> > +# See the COPYING file in the top-level directory.
> > +
> > +import sys, os
> > +from qmp import QEMUMonitorProtocol
> > +
> > +def print_response(rsp, prefix=[]):
> > +    if type(rsp) == list:
> > +        i = 0
> > +        for item in rsp:
> > +            if prefix == []:
> > +                prefix = ['item']
> > +            print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)])
> > +            i += 1
> > +    elif type(rsp) == dict:
> > +        for key in rsp.keys():
> > +            print_response(rsp[key], prefix + [key])
> > +    else:
> > +        if len(prefix):
> > +            print '%s: %s' % ('.'.join(prefix), rsp)
> > +        else:
> > +            print '%s' % (rsp)
> > +
> > +def main(args):
> > +    path = None
> > +
> > +    # Use QMP_PATH if it's set
> > +    if os.environ.has_key('QMP_PATH'):
> > +        path = os.environ['QMP_PATH']
> > +
> > +    while len(args):
> > +        arg = args[0]
> > +
> > +        if arg.startswith('--'):
> > +            arg = arg[2:]
> > +            if arg.find('=') == -1:
> > +                value = True
> > +            else:
> > +                arg, value = arg.split('=', 1)
> > +
> > +            if arg in ['path']:
> > +                path = value
> > +            elif arg in ['help']:
> > +                os.execlp('man', 'man', 'qmp')
> > +            else:
> > +                print 'Unknown argument "%s"' % arg
> > +
> > +            args = args[1:]
> > +        else:
> > +            break
> > +
> > +    if not path:
> > +        print "QMP path isn't set, use --path or set QMP_PATH"
> > +        return 1
> > +
> > +    command, args = args[0], args[1:]
> > +
> > +    if command in ['help']:
> > +        os.execlp('man', 'man', 'qmp')
> > +
> > +    srv = QEMUMonitorProtocol(path)
> > +    srv.connect()
> > +
> > +    def do_command(srv, cmd, **kwds):
> > +        rsp = srv.cmd(cmd, kwds)
> > +        if rsp.has_key('error'):
> > +            raise Exception(rsp['error']['desc'])
> > +        return rsp['return']
> > +
> > +    commands = map(lambda x: x['name'], do_command(srv, 'query-commands'))
> > +
> > +    srv.close()
> > +
> > +    if command not in commands:
> > +        fullcmd = 'qmp-%s' % command
> > +        try:
> > +            os.environ['QMP_PATH'] = path
> > +            os.execvp(fullcmd, [fullcmd] + args)
> > +        except OSError, (errno, msg):
> > +            if errno == 2:
> > +                print 'Command "%s" not found.' % (fullcmd)
> > +                return 1
> > +            raise
> > +        return 0
> > +
> > +    srv = QEMUMonitorProtocol(path)
> > +    srv.connect()
> > +
> > +    arguments = {}
> > +    for arg in args:
> > +        if not arg.startswith('--'):
> > +            print 'Unknown argument "%s"' % arg
> > +            return 1
> > +
> > +        arg = arg[2:]
> > +        if arg.find('=') == -1:
> > +            value = True
> > +        else:
> > +            arg, value = arg.split('=', 1)
> > +
> > +        if arg in ['help']:
> > +            os.execlp('man', 'man', 'qmp-%s' % command)
> > +            return 1
> > +
> > +        arguments[arg] = value
> > +
> > +    rsp = do_command(srv, command, **arguments)
> > +    print_response(rsp)
> > +
> > +if __name__ == '__main__':
> > +    sys.exit(main(sys.argv[1:]))
>
Anthony Liguori - Nov. 8, 2011, 1:57 p.m.
On 11/08/2011 02:36 AM, Mark Wu wrote:
> When I run this tool, I got two python exceptions. It turned out that both of
> them were caused by wrong usage. Do you think we need add validation for input
> to handle these cases?

Definitely.  Could you just send out a version of the patch with your changes, a 
(v2) in the subject, and your Signed-off-by?

Thanks!

Regards,

Anthony Liguori

>
> Thanks.
>
> 1. Not using '=' for path:
> $ ./QMP/qmp --path monitor-address
> Traceback (most recent call last):
> File "./QMP/qmp", line 120, in <module>
> sys.exit(main(sys.argv[1:]))
> File "./QMP/qmp", line 71, in main
> srv.connect()
> File "/home/mark/work/source/qemu/QMP/qmp.py", line 85, in connect
> self.__sock.connect(self.__address)
> File "<string>", line 1, in connect
> TypeError: argument must be string or read-only character buffer, not bool
>
> Proposed patch:
>
> @@ -48,7 +48,8 @@ def main(args):
> arg, value = arg.split('=', 1)
>
> if arg in ['path']:
> - path = value
> + if isinstance(value, basestring):
> + path = value
>
>
> 2. No qmp comand given in command line
> $ ./QMP/qmp --path=monitor-address
> Traceback (most recent call last):
> File "./QMP/qmp", line 120, in <module>
> sys.exit(main(sys.argv[1:]))
> File "./QMP/qmp", line 65, in main
> command, args = args[0], args[1:]
> IndexError: list index out of range
>
> @@ -62,11 +63,17 @@ def main(args):
> print "QMP path isn't set, use --path or set QMP_PATH"
> return 1
>
> Proposed patch:
> - command, args = args[0], args[1:]
> + if len(args):
> + command, args = args[0], args[1:]
> + else:
> + print 'No command found'
> + print 'Usage: "qmp [--path=monitor-address] qmp-cmd arguments"'
> + return 1
>
>
>
>
>
>
>
>
> On 11/07/2011 11:11 PM, Anthony Liguori wrote:
>> I wrote this quickly to aid in testing. It's similar to qmp-shell with a few
>> important differences:
>>
>> 1) It is not interactive. That makes it useful for scripting.
>>
>> 2) qmp-shell:
>>
>> (QEMU) set_password protocol=vnc password=foo
>>
>> 3) qmp:
>>
>> $ qmp set_password --protocol=vnc --password=foo
>>
>> 4) Extensible, git-style interface. If an invalid command name is passed, it
>> will try to exec qmp-$1.
>>
>> 5) It attempts to pretty print the JSON responses in a shell friendly format
>> such that tools can work with the output.
>>
>> Hope others will also find it useful.
>>
>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>> ---
>> QMP/qmp | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> 1 files changed, 120 insertions(+), 0 deletions(-)
>> create mode 100755 QMP/qmp
>>
>> diff --git a/QMP/qmp b/QMP/qmp
>> new file mode 100755
>> index 0000000..7b2a3c7
>> --- /dev/null
>> +++ b/QMP/qmp
>> @@ -0,0 +1,120 @@
>> +#!/usr/bin/python
>> +#
>> +# QMP command line tool
>> +#
>> +# Copyright IBM, Corp. 2011
>> +#
>> +# Authors:
>> +# Anthony Liguori<aliguori@us.ibm.com>
>> +#
>> +# This work is licensed under the terms of the GNU GPLv2 or later.
>> +# See the COPYING file in the top-level directory.
>> +
>> +import sys, os
>> +from qmp import QEMUMonitorProtocol
>> +
>> +def print_response(rsp, prefix=[]):
>> + if type(rsp) == list:
>> + i = 0
>> + for item in rsp:
>> + if prefix == []:
>> + prefix = ['item']
>> + print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)])
>> + i += 1
>> + elif type(rsp) == dict:
>> + for key in rsp.keys():
>> + print_response(rsp[key], prefix + [key])
>> + else:
>> + if len(prefix):
>> + print '%s: %s' % ('.'.join(prefix), rsp)
>> + else:
>> + print '%s' % (rsp)
>> +
>> +def main(args):
>> + path = None
>> +
>> + # Use QMP_PATH if it's set
>> + if os.environ.has_key('QMP_PATH'):
>> + path = os.environ['QMP_PATH']
>> +
>> + while len(args):
>> + arg = args[0]
>> +
>> + if arg.startswith('--'):
>> + arg = arg[2:]
>> + if arg.find('=') == -1:
>> + value = True
>> + else:
>> + arg, value = arg.split('=', 1)
>> +
>> + if arg in ['path']:
>> + path = value
>> + elif arg in ['help']:
>> + os.execlp('man', 'man', 'qmp')
>> + else:
>> + print 'Unknown argument "%s"' % arg
>> +
>> + args = args[1:]
>> + else:
>> + break
>> +
>> + if not path:
>> + print "QMP path isn't set, use --path or set QMP_PATH"
>> + return 1
>> +
>> + command, args = args[0], args[1:]
>> +
>> + if command in ['help']:
>> + os.execlp('man', 'man', 'qmp')
>> +
>> + srv = QEMUMonitorProtocol(path)
>> + srv.connect()
>> +
>> + def do_command(srv, cmd, **kwds):
>> + rsp = srv.cmd(cmd, kwds)
>> + if rsp.has_key('error'):
>> + raise Exception(rsp['error']['desc'])
>> + return rsp['return']
>> +
>> + commands = map(lambda x: x['name'], do_command(srv, 'query-commands'))
>> +
>> + srv.close()
>> +
>> + if command not in commands:
>> + fullcmd = 'qmp-%s' % command
>> + try:
>> + os.environ['QMP_PATH'] = path
>> + os.execvp(fullcmd, [fullcmd] + args)
>> + except OSError, (errno, msg):
>> + if errno == 2:
>> + print 'Command "%s" not found.' % (fullcmd)
>> + return 1
>> + raise
>> + return 0
>> +
>> + srv = QEMUMonitorProtocol(path)
>> + srv.connect()
>> +
>> + arguments = {}
>> + for arg in args:
>> + if not arg.startswith('--'):
>> + print 'Unknown argument "%s"' % arg
>> + return 1
>> +
>> + arg = arg[2:]
>> + if arg.find('=') == -1:
>> + value = True
>> + else:
>> + arg, value = arg.split('=', 1)
>> +
>> + if arg in ['help']:
>> + os.execlp('man', 'man', 'qmp-%s' % command)
>> + return 1
>> +
>> + arguments[arg] = value
>> +
>> + rsp = do_command(srv, command, **arguments)
>> + print_response(rsp)
>> +
>> +if __name__ == '__main__':
>> + sys.exit(main(sys.argv[1:]))
>
>

Patch

diff --git a/QMP/qmp b/QMP/qmp
new file mode 100755
index 0000000..7b2a3c7
--- /dev/null
+++ b/QMP/qmp
@@ -0,0 +1,120 @@ 
+#!/usr/bin/python
+#
+# QMP command line tool
+#
+# Copyright IBM, Corp. 2011
+#
+# Authors:
+#  Anthony Liguori <aliguori@us.ibm.com>
+#
+# This work is licensed under the terms of the GNU GPLv2 or later.
+# See the COPYING file in the top-level directory.
+
+import sys, os
+from qmp import QEMUMonitorProtocol
+
+def print_response(rsp, prefix=[]):
+    if type(rsp) == list:
+        i = 0
+        for item in rsp:
+            if prefix == []:
+                prefix = ['item']
+            print_response(item, prefix[:-1] + ['%s[%d]' % (prefix[-1], i)])
+            i += 1
+    elif type(rsp) == dict:
+        for key in rsp.keys():
+            print_response(rsp[key], prefix + [key])
+    else:
+        if len(prefix):
+            print '%s: %s' % ('.'.join(prefix), rsp)
+        else:
+            print '%s' % (rsp)
+
+def main(args):
+    path = None
+
+    # Use QMP_PATH if it's set
+    if os.environ.has_key('QMP_PATH'):
+        path = os.environ['QMP_PATH']
+
+    while len(args):
+        arg = args[0]
+
+        if arg.startswith('--'):
+            arg = arg[2:]
+            if arg.find('=') == -1:
+                value = True
+            else:
+                arg, value = arg.split('=', 1)
+
+            if arg in ['path']:
+                path = value
+            elif arg in ['help']:
+                os.execlp('man', 'man', 'qmp')
+            else:
+                print 'Unknown argument "%s"' % arg
+
+            args = args[1:]
+        else:
+            break
+
+    if not path:
+        print "QMP path isn't set, use --path or set QMP_PATH"
+        return 1
+
+    command, args = args[0], args[1:]
+
+    if command in ['help']:
+        os.execlp('man', 'man', 'qmp')
+
+    srv = QEMUMonitorProtocol(path)
+    srv.connect()
+
+    def do_command(srv, cmd, **kwds):
+        rsp = srv.cmd(cmd, kwds)
+        if rsp.has_key('error'):
+            raise Exception(rsp['error']['desc'])
+        return rsp['return']
+
+    commands = map(lambda x: x['name'], do_command(srv, 'query-commands'))
+
+    srv.close()
+
+    if command not in commands:
+        fullcmd = 'qmp-%s' % command
+        try:
+            os.environ['QMP_PATH'] = path
+            os.execvp(fullcmd, [fullcmd] + args)
+        except OSError, (errno, msg):
+            if errno == 2:
+                print 'Command "%s" not found.' % (fullcmd)
+                return 1
+            raise
+        return 0
+
+    srv = QEMUMonitorProtocol(path)
+    srv.connect()
+                
+    arguments = {}
+    for arg in args:
+        if not arg.startswith('--'):
+            print 'Unknown argument "%s"' % arg
+            return 1
+
+        arg = arg[2:]
+        if arg.find('=') == -1:
+            value = True
+        else:
+            arg, value = arg.split('=', 1)
+
+        if arg in ['help']:
+            os.execlp('man', 'man', 'qmp-%s' % command)
+            return 1
+
+        arguments[arg] = value
+
+    rsp = do_command(srv, command, **arguments)
+    print_response(rsp)
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))