Objectives
- Install proton and document any problems
- Create a program using messenger api (python)
Details about program
There are two separate apps.
- An Agent runs on a remote box and responds to requests
- get_owner - Return the current owner of the box
- set_owner - Set the current owner of the box
- get_info - Returns info about the box (using platform uname)
- A Beaker command line app that sends the above requests to an agent and displays the responses
Installation
- Google search for
qpid proton
- First result is http://qpid.apache.org/proton/
- Found Source Repository link.
- Installed using svn
- Found README and followed instructions.
Notes
This mostly went smoothly except for make install.As eallen
CMake Error at cmake_install.cmake:36 (FILE): file INSTALL cannot copy file "/home/remote/eallen/proton/LICENSE" to "/usr/share/proton-0.4/LICENSE".As root
UNEXPECTED ERROR: [Errno 13] Permission denied: '/home/remote/eallen/proton/build/proton-c/bindings/python/html/epydoc.css'Tried using bothcmake -DCMAKE_INSTALL_PREFIX=/usr ..andcmake -DCMAKE_INSTALL_PREFIX=/home/remote/eallen/myproton ..
Development
I quicky found the examples/messenger/py/* applications. Went through the README and send.py and recv.py worked as advertised.
However, since I needed a request/response pattern, I used the client.py and server.py as a starting point.
Design
- Send command in message subject
- Send any arguments in message properties
- Return data in message properties
- Use built-in reply-to as address of returned message
Notes
The only development problem I encountered was returning a list of values that contained a python None. No values after the None were received.
Output
$ agent.py -v
subscribed to amqp://~0.0.0.0:5678
get_info returned [0, ('Linux', 'redhat', '3.8.13-100.fc17.x86_64', '#1 SMP Mon May 13 13:36:17 UTC 2013', 'x86_64', 'x86_64')]
$ beaker.py 0.0.0.0:5678 get_info
Re: get_info {'result': [0L, ['Linux', 'redhat', '3.8.13-100.fc17.x86_64', '#1 SMP Mon May 13 13:36:17 UTC 2013', 'x86_64', 'x86_64']]}
Code
Agent
#!/usr/bin/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 sys, optparse
import platform
import inspect
from proton import *
class System:
def __init__(self, opts):
self.owner = None
self.info = platform.uname()
def get_owner(self, request):
_set_property(request, "owner", self.owner)
return 0, self.owner
def set_owner(self, request):
try:
owner = request.properties.get("owner")
self.owner = owner
return 0, owner
except AttributeError:
return -1, "message.properties must contain 'owner' when calling set_owner"
def clear_owner(self, request):
self.owner = None
return 0, None
def get_info(self, request):
return 0, self.info
def get_capabilities(self, request):
methods = inspect.getmembers(self, predicate=inspect.ismethod)
public = [m[0] for m in methods if not m[0].startswith("_")]
return 0, public
def _set_property(message, key, value):
if message.properties is None:
message.properties = dict()
message.properties[key] = value
class BeakerAgent():
def dispatch(self, request, response):
response.address = request.reply_to
response.correlation_id = request.correlation_id
response.body = request.body
if request.subject:
response.subject = "Re: %s" % request.subject
def subscribe(self, messenger, addresses, verbose):
subscribed = False
ex = None
for a in addresses:
try:
messenger.subscribe(a)
subscribed = True
if verbose:
print "subscribed to %s" % a
except MessengerException, e:
ex = e
if verbose:
print "failed to subscribe to %s (%s)" % (a, ex.message)
if not subscribed:
if ex:
raise ex
else:
raise Exception("not subscribed to an address")
def parse_cmd_line(self):
parser = optparse.OptionParser(usage="usage: %prog [options] ... ",
description="sample proton application")
parser.add_option("-v", "--verbose",
action="store_true", dest="verbose", default=False,
help="print log messages to stdout")
return parser.parse_args()
def main(self):
try:
opts, args = self.parse_cmd_line()
system = System(opts)
if not args:
args = ["amqp://~0.0.0.0:5678"]
messenger = Messenger()
messenger.start()
self.subscribe(messenger, args, opts.verbose)
request = Message()
reply = Message()
running = True
while running:
if messenger.incoming < 2:
try:
messenger.recv(2)
except KeyboardInterrupt:
running = False
if opts.verbose:
print "KeyboardInterrupt. Exiting"
if messenger.incoming > 0:
messenger.get(request)
if request.reply_to:
value = None
code = 0
method = getattr(system, request.subject, None)
if callable(method):
code, value = method(request)
else:
code = -1
value = "%s not implemented" % request.subject
if opts.verbose:
print "%s returned [%d, %s]" % (request.subject, code, value)
_set_property(reply, "result", [code, value])
self.dispatch(request, reply)
messenger.put(reply)
messenger.send()
messenger.stop()
return 0
except Exception, e:
sys.stderr.write("%s\n" % e.message)
return -1
def daemon_main():
agent = BeakerAgent()
sys.exit(agent.main())
if __name__ == "__main__":
daemon_main()
Beaker
#!/usr/bin/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 sys from optparse import OptionParser, OptionGroup, IndentedHelpFormatter import getpass from proton import * usage = """ Usage: %progaddr: address of system command: get_owner | set_owner | get_info """ description = """ Communicates with a beaker-agent (beaker-agent.py) running at an address. Examples: $ %prog 0.0.0.0 get_owner => asks the agent at 0.0.0.0 to return its owner $ %prog mrg17.lab.bos.redhat.com:5673 get_info => asks the agent at mrg17.lab.bos.redhat.com:5673 to return its system info $ %prog mrg17.lab.bos.redhat.com:5673 set_owner => tells the agent at mrg17.lab.bos.redhat.com:5673 to set its owner to your username """ class HelpFormatter(IndentedHelpFormatter): """Format usage and description without stripping newlines from usage strings """ def format_usage(self, usage): return usage def format_description(self, description): if description: return description + "\n" else: return "" parser = OptionParser(usage=usage, description=description, formatter=HelpFormatter()) parser.add_option("-r", "--reply_to", default="~/replies", help="address: [amqp://] [/ ] (default %default)") opts, args = parser.parse_args() if len(args) != 2: parser.error("incorrect number of arguments") address, command = args mng = Messenger() mng.start() msg = Message() msg.address = address msg.subject = command if command == "set_owner": user = getpass.getuser() msg.properties = dict() msg.properties["owner"] = user msg.reply_to = opts.reply_to mng.put(msg) mng.send() if opts.reply_to[:2] == "~/": mng.recv(1) try: mng.get(msg) print msg.subject, msg.properties except Exception, e: print e mng.stop()
Python Messaging Example