On 12/08/2010 11:37 PM, Jonathan Robie wrote:
I was talking with Ted about qpid-config and qpid-route, and we
thought it might be useful to have a simple, consistent syntax using
one set of conventions for both utilities.

The syntax you propose is really only more consistent in that the verb and the noun are in the same order (which certainly makes sense). Other than that there isn't a great deal of consistency between, for example:

 declare exchange a topic

and

 declare route from broker=localhost:10001 exchange=amq.topic
    key=global.# to broker=localhost:10002

In some sense qpid-config and qpid-route actually have a different purpose. The former is concerned with listing, creating or deleting objects on a single broker. The latter, it seems to me, is concerned with presenting a higher level conceptual view of routing between two (or more) brokers (how successful it is in that is open to debate).

I think before changing anything like this we should start by re-examining the rationale for each tool we have.

Some other developers suggested that using optparse for all the Python
command line utilities might lead to more consistency. That helps with
most utilities, but not with qpid-config and qpid-route, which each
have a syntax based on sequences of strings.

They do in addition have lots of options, however and a consistent approach to those is still relevant.

The use of optparse throughout primarily unifies and simplifies the code for maintainers. The benefit to users is that it avoids inconsistent weirdness in actual option *parsing*; it doesn't in and of itself result in consistent options being chosen. Both things are beneficial however.

Interpretation of arguments (as opposed to options) is a third issue (and is certainly also important).

In this discussion, it became clear that different people are thinking
of different use cases, and it's not completely clear what the real
requirements are or which solution would be best.

Here are some requirements that have been mentioned:

- We need a consistent syntax.

- A consistent API that corresponds to the commands these tools offer
would be helpful. All of the commands in qpid-config and qpid-route
are also useful within programs

- Machine-readable logging is important for some applications.

- It's important to be able to run these commands in batch mode. Start
up time is significant, so running a sequence of Python command-line
utilities in a batch file is slow.

One solution: improve start-up time.

(The use of the QMF API in qpid-config for example seems to add an order of magnitude to the time taken to e.g. declare a queue over a simpler program that does the same thing...)

Another solution: design a Python API for these tasks, and use a
Python script, with one connection, rather than a batch file.

Another solution: allow a sequence of qpid-config or qpid-route
commands to be placed in a file and executed in batch mode.
>
- Running commands interactively, in a command shell, and using the
same commands in a batch file. That makes it easier to write / debug
scripts.

What other requirements / use cases should we keep in mind?

One thing that none of the command line tools do at present is provide an easy way to invoke arbitrary management methods (e.g. purge, move etc). I recently came across this when adding a command to change the log-level.

What other questions should we be asking?

The functionality we need to support is illustrated by the commands
below my signature. We might choose to support this syntax in some
form, or perhaps support an API that corresponds to these commands, or
perhaps support both, with a direct mapping between the two. Are there
other approaches we should consider?

I think we should have a standard approach to invoking management commands (QMF methods at present)[1]. That in itself defines an API. We then need to carefully consider how exactly to expose various operations through the schema to ensure usability and consistency overall.

The querying and listing of management objects is the other part of the picture[2]. Consistency there is fairly easy to achieve I think.

[1] I checked in a little test program to do this (currently in cpp/src/tests/qpid-ctrl). I needed it specifically to test runtime changes to log-level. An additional objective was to explore the use of a message based approach to programmatic broker management (i.e. initially at least, through direct use of QMFv2).

You get a minimal usage statement from qpid-ctrl --help. Essentially you supply the QMF method name and then key-value pairs for any parameters as arguments. There are options to choose the broker to connect to and some other operational aspects. By default the methods are assumed to be intended to be invoked on the Broker object itself E.g.

  qpid-ctrl setLogLevel level=info+
  qpid-ctrl getLogLevel

  qpid-ctrl queueMoveMessages srcQueue=a destQueue=b qty=3

  qpid-ctrl connect host=localhost port=5673

However you can also invoke methods on any object by providing the class of the object and an identifier for it. E.g.

  qpid-ctrl --class queue --id my-queue purge request=5

This tool would need quite a bit more work before it would be ready for consideration as something other than a test utility and the details of the implementation are not necessarily the best choices. However I think its a good demonstration of the basic concept of focusing on a usable and consistent management schema as a way to drive consistency and uniformity in tool support for that schema.

[2] Attached is a *very* rough example program that uses QMFv2 to list objects. This was really an experiment I did for my own purposes but it may be of interest to this thread in conjunction with qpid-ctrl.
#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
# 
#   http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#

import optparse
from qpid.messaging import *
from qpid.util import URL
from qpid.log import enable, DEBUG, WARN

def list_map_entries(m):
  r = ""
  for t in m:
    r += "%s=%s " % (t, m[t]) 
  return r

def get_qmfv2_result(m):
  if m.properties['x-amqp-0-10.app-id'] == 'qmf2':
    if m.properties['qmf.opcode'] == '_method_response':
      return m.content['_arguments']
    elif m.properties['qmf.opcode'] == '_exception':
      raise Exception("Error: %s" % list_map_entries(m.content['_values']))
    else: raise Exception("Invalid response received, unexpected opcode: %s" % 
m)
  else: raise Exception("Invalid response received, not a qmfv2 method: %s" % m)


parser = optparse.OptionParser(usage="usage: %prog [options]",
                               description="List managed objects.")
parser.add_option("-b", "--broker", default="localhost",
                  help="connect to specified BROKER (default %default)")
parser.add_option("-c", "--class", dest="qmfclass", default="", 
                  help="class of object on which command is being invoked 
(default %default)")
parser.add_option("-p", "--package", default="",
                  help="package of object on which command is being invoked 
(default %default)")
parser.add_option("-i", "--id", default="",
                  help="identifier of object on which command is being invoked 
(default %default)")
parser.add_option("-a", "--address", default="qmf.default.direct/broker",
                  help="address to send commands to (default %default)")
parser.add_option("-t", "--timeout", type="float", default=5,
                  help="timeout in seconds to wait for response before exiting 
(default %default)")
parser.add_option("-l", dest="detailed", action="store_true",
                  help="display all properties of listed objects")
parser.add_option("-v", dest="verbose", action="store_true",
                  help="enable logging")

opts, args = parser.parse_args()

if opts.verbose:
  enable("qpid", DEBUG)
else:
  enable("qpid", WARN)

conn = Connection(opts.broker)
try:
  conn.open()
  ssn = conn.session()
  snd = ssn.sender(opts.address)
  reply_to = "qmf.default.direct/%s; {node: {type: topic}}" % str(uuid4())
  rcv = ssn.receiver(reply_to)

  content = {"_what": "OBJECT"}
  if opts.qmfclass:
    if opts.id:
      if opts.package:
        package = opts.package
      else:
        package = "org.apache.qpid.broker"
      object_name = "%s:%s:%s" % (package, opts.qmfclass, opts.id)
      content["_object_id"] = {"_object_name": object_name}
    else:
      content["_schema_id"] = {"_class_name": opts.qmfclass}
      if opts.package: content["_schema_id"]["_package_name"] = opts.package
  msg = Message(reply_to=reply_to, content=content)
  msg.properties["x-amqp-0-10.app-id"] = "qmf2"
  msg.properties["qmf.opcode"] = "_query_request"
  snd.send(msg)

  try:
    result = rcv.fetch(timeout=opts.timeout)
    items = result.content
    for i in items:
      n = i["_object_id"]["_object_name"]
      print n.split(':', 2)[2]
      if opts.detailed:
        props = i["_values"]
        for k in props:
          print "    %s = %s" % (k, props[k])
  except Empty:
    print "No response received!"
  except Exception, e:
    print e
except ReceiverError, e:
  print e
except KeyboardInterrupt:
  pass

conn.close()

---------------------------------------------------------------------
Apache Qpid - AMQP Messaging Implementation
Project:      http://qpid.apache.org
Use/Interact: mailto:[email protected]

Reply via email to