From ec790a3bed269c60edfd5baa41e55e7fad9ce5c5 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Wed, 25 Mar 2015 10:12:29 +0100 Subject: [PATCH] Introduction of oslo.messaging * replaces kombu with oslo.messaging * deprecates and removes the faafo-tracker service (merged into the faafo-producer service) * moves all CLI options into configuration files * updates log example outputs in the documentation * updates the workflow diagram and removes the workflow text description Change-Id: I4b79ed28a8f5b1fe1b33dfdbfe9cc8c958f80b1b --- ansible/files/api.conf | 4 ++ ansible/files/producer.conf | 5 ++ ansible/files/run_api.sh | 4 +- ansible/files/run_producer.sh | 5 +- ansible/files/run_tracker.sh | 6 --- ansible/files/run_worker.sh | 5 +- ansible/files/worker.conf | 5 ++ ansible/playbook.yaml | 12 +++-- doc/source/development.rst | 10 ++-- doc/source/images/diagram.dot | 4 +- doc/source/images/diagram.png | Bin 40287 -> 36032 bytes doc/source/usage.rst | 42 +++++---------- doc/source/workflow.rst | 14 ----- etc/faafo/producer.conf | 1 + etc/faafo/worker.conf | 1 + faafo/api.py | 8 +-- faafo/producer.py | 41 +++++++------- faafo/queues.py | 20 ------- faafo/tracker.py | 98 --------------------------------- faafo/worker.py | 99 ++++++++++++++-------------------- requirements.txt | 4 +- setup.cfg | 1 - 22 files changed, 110 insertions(+), 279 deletions(-) create mode 100644 ansible/files/api.conf create mode 100644 ansible/files/producer.conf delete mode 100644 ansible/files/run_tracker.sh create mode 100644 ansible/files/worker.conf create mode 100644 etc/faafo/producer.conf create mode 100644 etc/faafo/worker.conf delete mode 100644 faafo/queues.py delete mode 100644 faafo/tracker.py diff --git a/ansible/files/api.conf b/ansible/files/api.conf new file mode 100644 index 0000000..498c7c7 --- /dev/null +++ b/ansible/files/api.conf @@ -0,0 +1,4 @@ +[DEFAULT] + +database_url = mysql://faafo:secretsecret@127.0.0.1:3306/faafo +verbose = True diff --git a/ansible/files/producer.conf b/ansible/files/producer.conf new file mode 100644 index 0000000..d6cbc70 --- /dev/null +++ b/ansible/files/producer.conf @@ -0,0 +1,5 @@ +[DEFAULT] + +endpoint_url = http://127.0.0.1:5000 +transport_url = rabbit://faafo:secretsecret@localhost:5672/ +verbose = True diff --git a/ansible/files/run_api.sh b/ansible/files/run_api.sh index e36f86d..944264e 100644 --- a/ansible/files/run_api.sh +++ b/ansible/files/run_api.sh @@ -1,5 +1,3 @@ #!/bin/sh -faafo-api \ - --database-url mysql://faafo:secretsecret@127.0.0.1:3306/faafo \ - --debug --verbose +faafo-api --config-file api.conf diff --git a/ansible/files/run_producer.sh b/ansible/files/run_producer.sh index e1c8048..83e9b9c 100644 --- a/ansible/files/run_producer.sh +++ b/ansible/files/run_producer.sh @@ -1,6 +1,3 @@ #!/bin/sh -faafo-producer \ - --amqp-url amqp://faafo:secretsecret@127.0.0.1:5672/ \ - --api-url http://127.0.0.1:5000 \ - --debug --verbose +faafo-producer --config-file producer.conf diff --git a/ansible/files/run_tracker.sh b/ansible/files/run_tracker.sh deleted file mode 100644 index 4c57117..0000000 --- a/ansible/files/run_tracker.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -faafo-tracker \ - --amqp-url amqp://faafo:secretsecret@127.0.0.1:5672/ \ - --api-url http://127.0.0.1:5000 \ - --debug --verbose diff --git a/ansible/files/run_worker.sh b/ansible/files/run_worker.sh index bc4b4b6..b2041a9 100644 --- a/ansible/files/run_worker.sh +++ b/ansible/files/run_worker.sh @@ -1,6 +1,3 @@ #!/bin/sh -faafo-worker \ - --amqp-url amqp://faafo:secretsecret@127.0.0.1:5672/ \ - --target /home/vagrant \ - --debug --verbose +faafo-worker --config-file worker.conf diff --git a/ansible/files/worker.conf b/ansible/files/worker.conf new file mode 100644 index 0000000..8e42ae3 --- /dev/null +++ b/ansible/files/worker.conf @@ -0,0 +1,5 @@ +[DEFAULT] + +filesystem_store_datadir = /home/vagrant +transport_url = rabbit://faafo:secretsecret@localhost:5672/ +verbose = True diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml index 645972e..430447d 100644 --- a/ansible/playbook.yaml +++ b/ansible/playbook.yaml @@ -20,7 +20,11 @@ args: chdir: /vagrant - - copy: src=files/run_api.sh dest=/home/vagrant/run_api.sh mode=0755 owner=vagrant group=vagrant - - copy: src=files/run_producer.sh dest=/home/vagrant/run_producer.sh mode=0755 owner=vagrant group=vagrant - - copy: src=files/run_tracker.sh dest=/home/vagrant/run_tracker.sh mode=0755 owner=vagrant group=vagrant - - copy: src=files/run_worker.sh dest=/home/vagrant/run_worker.sh mode=0755 owner=vagrant group=vagrant + - copy: src=files/{{ item }} dest=/home/vagrant/{{ item }} mode=0755 owner=vagrant group=vagrant + with_items: + - api.conf + - producer.conf + - run_api.sh + - run_producer.sh + - run_worker.sh + - worker.conf diff --git a/doc/source/development.rst b/doc/source/development.rst index 9777f49..1c8b909 100644 --- a/doc/source/development.rst +++ b/doc/source/development.rst @@ -27,12 +27,11 @@ Now it is possible to login with SSH. $ vagrant ssh -Open a new screen or tmux session. Aftwards run the api, worker, producer, and -tracker services in the foreground, each service in a separate window. +Open a new screen or tmux session. Aftwards run the api, worker, and producer +services in the foreground, each service in a separate window. * :code:`sh run_api.sh` * :code:`sh run_producer.sh` -* :code:`sh run_tracker.sh` * :code:`sh run_worker.sh` RabbitMQ server @@ -60,12 +59,11 @@ the application itself. $ pip install -r requirements.txt $ python setup.py install -Open a new screen or tmux session. Aftwards run the api, worker, producer, and -tracker services in the foreground, each service in a separate window. +Open a new screen or tmux session. Aftwards run the api, worker, and producer +services in the foreground, each service in a separate window. .. code:: $ source .venv/bin/activate; faafo-api $ source .venv/bin/activate; faafo-worker - $ source .venv/bin/activate; faafo-tracker $ source .venv/bin/activate; faafo-producer diff --git a/doc/source/images/diagram.dot b/doc/source/images/diagram.dot index 8c1f786..f1689a9 100644 --- a/doc/source/images/diagram.dot +++ b/doc/source/images/diagram.dot @@ -4,12 +4,12 @@ digraph { Database -> API [color=red]; API -> Webinterface [color=red]; Producer -> API [color=orange]; + Producer -> API [color=green]; Producer -> "Queue Service" [color=orange]; - Tracker -> API [color=green]; - "Queue Service" -> Tracker [color=green]; "Queue Service" -> Worker [color=orange]; Worker -> "Image File" [color=blue]; Worker -> "Queue Service" [color=green]; + "Queue Service" -> Producer [color=green]; "Image File" -> "Storage Backend" [color=blue]; "Storage Backend" -> Webinterface [color=red]; } diff --git a/doc/source/images/diagram.png b/doc/source/images/diagram.png index afe39742a1037599d46be68fc2e34535f63e5422..ea27612d02f0a625153b4232f15de283c84a621c 100644 GIT binary patch literal 36032 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 D9!~uk literal 40287 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 Aw*UYD diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 86916cf..d975ff5 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -10,42 +10,28 @@ Example image Example outputs --------------- -API Service -~~~~~~~~~~~ - -FIXME(berendt): add output of the API service - Producer service ~~~~~~~~~~~~~~~~ -FIXME(berendt): update output (introduction of oslo.logging) - .. code:: - 2015-02-12 22:21:42,870 generating 2 task(s) - 2015-02-12 22:21:42,876 generated task: {'width': 728, 'yb': 2.6351683415972076, 'uuid': UUID('66d5f67e-d26d-42fb-9d88-3c3830b4187a'), 'iterations': 395, 'xb': 1.6486035545865234, 'xa': -1.2576814065507933, 'ya': -2.8587178863035616, 'height': 876} - 2015-02-12 22:21:42,897 generated task: {'width': 944, 'yb': 2.981696583462036, 'uuid': UUID('6f873111-8bc2-4d73-9a36-ed49915699c8'), 'iterations': 201, 'xb': 3.530775320058914, 'xa': -3.3511031734533794, 'ya': -0.921920674639712, 'height': 962} - 2015-02-12 22:21:42,927 sleeping for 2.680171 seconds - -Tracker service -~~~~~~~~~~~~~~~ - -FIXME(berendt): update output (introduction of oslo.logging) - -.. code:: - - 2015-02-12 22:20:26,630 processing result be42a131-e4aa-4db5-80d1-1956784f4b81 - 2015-02-12 22:20:26,630 elapsed time 5.749099 seconds - 2015-02-12 22:20:26,631 checksum 7ba5bf955a94f1aa02e5f442869b8db88a5915b7c2fb91ffba74708b8d799c2a + 2015-03-25 23:01:29.308 22526 INFO faafo.producer [-] generating 1 task(s) + 2015-03-25 23:01:29.344 22526 INFO faafo.producer [-] generated task: {'width': 510, 'yb': 2.478654026560605, 'uuid': '212e8c23-e67f-4bd3-86e1-5a5e811ee2f4', 'iterations': 281, 'xb': 1.1428457603077387, 'xa': -3.3528957195683087, 'ya': -2.1341119130263717, 'height': 278} + 2015-03-25 23:01:30.295 22526 INFO faafo.producer [-] task 212e8c23-e67f-4bd3-86e1-5a5e811ee2f4 processed: {u'duration': 0.8725259304046631, u'checksum': u'b22d975c4f9dc77df5db96ce6264a4990d865dd8f800aba2ac03a065c2f09b1e', u'uuid': u'212e8c23-e67f-4bd3-86e1-5a5e811ee2f4'} Worker service ~~~~~~~~~~~~~~ -FIXME(berendt): update output (introduction of oslo.logging) - .. code:: - 2015-02-12 22:20:59,258 processing task 20a00e9e-baec-4045-bc57-2cb9d8d1aa61 - 2015-02-12 22:21:01,506 task 20a00e9e-baec-4045-bc57-2cb9d8d1aa61 processed in 2.246601 seconds - 2015-02-12 22:21:01,553 saved result of task 20a00e9e-baec-4045-bc57-2cb9d8d1aa61 to file /home/vagrant/20a00e9e-baec-4045-bc57-2cb9d8d1aa61.png - 2015-02-12 22:21:01,554 pushed result: {'duration': 2.246600866317749, 'checksum': 'faa0f00a72fac53e02c3eb392c5da8365139e509899e269227e5c27047af6c1f', 'uuid': UUID('20a00e9e-baec-4045-bc57-2cb9d8d1aa61')} + 2015-03-25 23:01:29.378 22518 INFO faafo.worker [-] processing task 212e8c23-e67f-4bd3-86e1-5a5e811ee2f4 + 2015-03-25 23:01:30.251 22518 INFO faafo.worker [-] task 212e8c23-e67f-4bd3-86e1-5a5e811ee2f4 processed in 0.872526 seconds + 2015-03-25 23:01:30.268 22518 INFO faafo.worker [-] saved result of task 212e8c23-e67f-4bd3-86e1-5a5e811ee2f4 to file /home/vagrant/212e8c23-e67f-4bd3-86e1-5a5e811ee2f4.png + + +API Service +~~~~~~~~~~~ +.. code:: + + 2015-03-25 23:01:29.342 22511 INFO werkzeug [-] 127.0.0.1 - - [25/Mar/2015 23:01:29] "POST /api/fractal HTTP/1.1" 201 - + 2015-03-25 23:01:30.317 22511 INFO werkzeug [-] 127.0.0.1 - - [25/Mar/2015 23:01:30] "PUT /api/fractal/212e8c23-e67f-4bd3-86e1-5a5e811ee2f4 HTTP/1.1" 200 - diff --git a/doc/source/workflow.rst b/doc/source/workflow.rst index eafbd74..9f642dc 100644 --- a/doc/source/workflow.rst +++ b/doc/source/workflow.rst @@ -2,17 +2,3 @@ Workflow -------- .. image:: images/diagram.png - -FIXME(berendt): Add new API service and webinterface to the workflow description. - -* The producer generates a random number of tasks with random parameters and a UUID as identifier. -* The producer pushes the generated tasks into the exchange :code:`tasks`. -* The producer inserts a new record for each task into the database (including all parameters and the UUID). -* The producer sleeps for a random number of seconds and will generate more tasks after awakening. -* All messages in the :code:`tasks` exchange will be routed into the :code:`tasks` queue. -* The worker waits for new messages in the :code:`tasks` queue. -* After receiving a message the worker generates an image based on the received parameters and writes the result into a local file (identified by the UUID). -* After writing an image the worker pushes the result (the checksum of the generated image and the duration identified by the UUID) into the exchange :code:`results`. -* All messages in the :code:`results` exchange will be routed into the :code:`results` queue. -* The tracker waits for new messages in the :code:`results` queue. -* After receiving a message the tracker updates the duration and checksum value of the corresponding database record (identified by the UUID). diff --git a/etc/faafo/producer.conf b/etc/faafo/producer.conf new file mode 100644 index 0000000..f5df3e6 --- /dev/null +++ b/etc/faafo/producer.conf @@ -0,0 +1 @@ +TODO(berendt): generate example configuration file with http://docs.openstack.org/developer/oslo.config/generator.html diff --git a/etc/faafo/worker.conf b/etc/faafo/worker.conf new file mode 100644 index 0000000..f5df3e6 --- /dev/null +++ b/etc/faafo/worker.conf @@ -0,0 +1 @@ +TODO(berendt): generate example configuration file with http://docs.openstack.org/developer/oslo.config/generator.html diff --git a/faafo/api.py b/faafo/api.py index 298c03c..5959a2f 100644 --- a/faafo/api.py +++ b/faafo/api.py @@ -18,9 +18,9 @@ from oslo_log import log from faafo import version -LOG = log.getLogger(__name__) +LOG = log.getLogger('faafo.api') -cli_opts = [ +api_opts = [ cfg.StrOpt('listen-address', default='0.0.0.0', help='Listen address.'), @@ -32,7 +32,7 @@ cli_opts = [ help='Database connection URL.') ] -cfg.CONF.register_cli_opts(cli_opts) +cfg.CONF.register_opts(api_opts) log.register_options(cfg.CONF) log.set_defaults() @@ -43,7 +43,7 @@ cfg.CONF(project='api', prog='faafo-api', log.setup(cfg.CONF, 'api', version=version.version_info.version_string()) -app = flask.Flask(__name__) +app = flask.Flask('faafo.api') app.config['DEBUG'] = cfg.CONF.debug app.config['SQLALCHEMY_DATABASE_URI'] = cfg.CONF.database_url db = flask.ext.sqlalchemy.SQLAlchemy(app) diff --git a/faafo/producer.py b/faafo/producer.py index eed19cf..6e41ebe 100644 --- a/faafo/producer.py +++ b/faafo/producer.py @@ -16,27 +16,19 @@ import json import random import uuid -import kombu -from kombu.pools import producers from oslo_config import cfg from oslo_log import log +import oslo_messaging as messaging import requests from faafo.openstack.common import periodic_task from faafo.openstack.common import service -from faafo import queues from faafo import version LOG = log.getLogger('faafo.producer') cli_opts = [ - cfg.StrOpt('amqp-url', - default='amqp://faafo:secretsecret@localhost:5672/', - help='AMQP connection URL'), - cfg.StrOpt('api-url', - default='http://localhost:5000', - help='API connection URL') ] producer_opts = [ @@ -74,22 +66,27 @@ producer_opts = [ help="The minimum number of generated tasks."), cfg.IntOpt("max-tasks", default=10, help="The maximum number of generated tasks."), - cfg.IntOpt("interval", default=10, help="Interval in seconds.") + cfg.IntOpt("interval", default=10, help="Interval in seconds."), + cfg.StrOpt('endpoint-url', + default='http://localhost:5000', + help='API connection URL') ] -cfg.CONF.register_cli_opts(cli_opts) -cfg.CONF.register_cli_opts(producer_opts) +cfg.CONF.register_opts(producer_opts) class ProducerService(service.Service, periodic_task.PeriodicTasks): def __init__(self): super(ProducerService, self).__init__() - self.messaging = kombu.Connection(cfg.CONF.amqp_url) self._periodic_last_run = {} + transport = messaging.get_transport(cfg.CONF) + target = messaging.Target(topic='tasks') + self._client = messaging.RPCClient(transport, target) @periodic_task.periodic_task(spacing=cfg.CONF.interval, - run_immediately=True) - def generate_task(self, context): + run_immediately=False) + def generate_task(self, ctxt): + ctxt = {} random.seed() number = random.randint(cfg.CONF.min_tasks, cfg.CONF.max_tasks) LOG.info("generating %d task(s)" % number) @@ -98,16 +95,14 @@ class ProducerService(service.Service, periodic_task.PeriodicTasks): # NOTE(berendt): only necessary when using requests < 2.4.2 headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} - requests.post("%s/api/fractal" % cfg.CONF.api_url, + requests.post("%s/api/fractal" % cfg.CONF.endpoint_url, json.dumps(task), headers=headers) LOG.info("generated task: %s" % task) - with producers[self.messaging].acquire(block=True) as producer: - producer.publish( - task, - serializer='pickle', - exchange=queues.task_exchange, - declare=[queues.task_exchange], - routing_key='tasks') + result = self._client.call(ctxt, 'process', task=task) + LOG.info("task %s processed: %s" % (task['uuid'], result)) + requests.put("%s/api/fractal/%s" % + (cfg.CONF.endpoint_url, str(task['uuid'])), + json.dumps(result), headers=headers) self.add_periodic_task(generate_task) self.tg.add_dynamic_timer(self.periodic_tasks) diff --git a/faafo/queues.py b/faafo/queues.py deleted file mode 100644 index 7ac420b..0000000 --- a/faafo/queues.py +++ /dev/null @@ -1,20 +0,0 @@ -# Licensed 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. - -from kombu import Exchange -from kombu import Queue - -task_exchange = Exchange('tasks', type='direct') -task_queues = [Queue('tasks', task_exchange, routing_key='tasks')] - -result_exchange = Exchange('results', type='direct') -result_queues = [Queue('results', result_exchange, routing_key='results')] diff --git a/faafo/tracker.py b/faafo/tracker.py deleted file mode 100644 index f678451..0000000 --- a/faafo/tracker.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python - -# Licensed 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. - -# based on http://code.activestate.com/recipes/577120-julia-fractals/ - -import json -import sys - -import daemon -import kombu -from kombu.mixins import ConsumerMixin -from oslo_config import cfg -from oslo_log import log -import requests - -from faafo import queues -from faafo import version - - -LOG = log.getLogger(__name__) - -cli_opts = [ - cfg.BoolOpt('daemonize', - default=False, - help='Run in background.'), - cfg.StrOpt('amqp-url', - default='amqp://faafo:secretsecret@localhost:5672/', - help='AMQP connection URL'), - cfg.StrOpt('api-url', - default='http://localhost:5000', - help='API connection URL') -] - -cfg.CONF.register_cli_opts(cli_opts) - - -class Tracker(ConsumerMixin): - - def __init__(self, amqp_url, api_url): - self.connection = kombu.Connection(amqp_url) - self.api_url = api_url - - def get_consumers(self, Consumer, channel): - return [Consumer(queues=queues.result_queues, - accept=['pickle', 'json'], - callbacks=[self.process_result])] - - def process_result(self, body, message): - LOG.info("processing result %s" % body['uuid']) - LOG.info("elapsed time %f seconds" % body['duration']) - LOG.info("checksum %s" % body['checksum']) - result = { - 'duration': float(body['duration']), - 'checksum': str(body['checksum']) - } - # NOTE(berendt): only necessary when using requests < 2.4.2 - headers = {'Content-type': 'application/json', - 'Accept': 'text/plain'} - requests.put("%s/api/fractal/%s" % - (self.api_url, str(body['uuid'])), - json.dumps(result), headers=headers) - message.ack() - - -def main(): - log.register_options(cfg.CONF) - log.set_defaults() - - cfg.CONF(project='tracker', prog='faafo-tracker', - version=version.version_info.version_string()) - - log.setup(cfg.CONF, 'tracker', - version=version.version_info.version_string()) - - tracker = Tracker(cfg.CONF.amqp_url, cfg.CONF.api_url) - - if cfg.CONF.daemonize: - with daemon.DaemonContext(): - tracker.run() - else: - try: - tracker.run() - except Exception as e: - sys.exit("ERROR: %s" % e) - -if __name__ == '__main__': - main() diff --git a/faafo/worker.py b/faafo/worker.py index 3f12380..8a64989 100644 --- a/faafo/worker.py +++ b/faafo/worker.py @@ -14,39 +14,33 @@ # based on http://code.activestate.com/recipes/577120-julia-fractals/ +import eventlet +eventlet.monkey_patch() + import hashlib import os from PIL import Image import random -import sys +import socket import time -import daemon -import kombu -from kombu.mixins import ConsumerMixin -from kombu.pools import producers from oslo_config import cfg from oslo_log import log +import oslo_messaging as messaging -from faafo import queues from faafo import version -LOG = log.getLogger(__name__) +LOG = log.getLogger('faafo.worker') -cli_opts = [ - cfg.BoolOpt('daemonize', - default=False, - help='Run in background.'), - cfg.StrOpt('target', +worker_opts = [ + cfg.StrOpt('filesystem_store_datadir', default='/tmp', - help='Target directory for fractal image files.'), - cfg.StrOpt('amqp-url', - default='amqp://faafo:secretsecret@localhost:5672/', - help='AMQP connection URL') + help='Directory that the filesystem backend store writes ' + 'fractal image files to.'), ] -cfg.CONF.register_cli_opts(cli_opts) +cfg.CONF.register_opts(worker_opts) class JuliaSet(object): @@ -97,49 +91,34 @@ class JuliaSet(object): return (c, z) -class Worker(ConsumerMixin): +class WorkerEndpoint(object): - def __init__(self, url, target): - self.connection = kombu.Connection(url) - self.target = target - - def get_consumers(self, Consumer, channel): - return [Consumer(queues=queues.task_queues, - accept=['pickle', 'json'], - callbacks=[self.process_task])] - - def process_task(self, body, message): - LOG.info("processing task %s" % body['uuid']) - LOG.debug(body) + def process(self, ctxt, task): + LOG.info("processing task %s" % task['uuid']) + LOG.debug(task) start_time = time.time() - juliaset = JuliaSet(body['width'], - body['height'], - body['xa'], - body['xb'], - body['ya'], - body['yb'], - body['iterations']) - filename = os.path.join(self.target, "%s.png" % body['uuid']) + juliaset = JuliaSet(task['width'], + task['height'], + task['xa'], + task['xb'], + task['ya'], + task['yb'], + task['iterations']) + filename = os.path.join(cfg.CONF.filesystem_store_datadir, + "%s.png" % task['uuid']) elapsed_time = time.time() - start_time LOG.info("task %s processed in %f seconds" % - (body['uuid'], elapsed_time)) + (task['uuid'], elapsed_time)) juliaset.save(filename) LOG.info("saved result of task %s to file %s" % - (body['uuid'], filename)) + (task['uuid'], filename)) checksum = hashlib.sha256(open(filename, 'rb').read()).hexdigest() result = { - 'uuid': body['uuid'], + 'uuid': task['uuid'], 'duration': elapsed_time, 'checksum': checksum } - LOG.info("pushed result: %s" % result) - with producers[self.connection].acquire(block=True) as producer: - producer.publish(result, serializer='pickle', - exchange=queues.result_exchange, - declare=[queues.result_exchange], - routing_key='results') - - message.ack() + return result def main(): @@ -152,16 +131,18 @@ def main(): log.setup(cfg.CONF, 'worker', version=version.version_info.version_string()) - worker = Worker(cfg.CONF.amqp_url, cfg.CONF.target) - - if cfg.CONF.daemonize: - with daemon.DaemonContext(): - worker.run() - else: - try: - worker.run() - except Exception as e: - sys.exit("ERROR: %s" % e) + transport = messaging.get_transport(cfg.CONF) + target = messaging.Target(topic='tasks', server=socket.gethostname()) + endpoints = [ + WorkerEndpoint() + ] + server = messaging.get_rpc_server(transport, target, endpoints, + executor='eventlet') + server.start() + try: + server.wait() + except KeyboardInterrupt: + LOG.info("Caught keyboard interrupt. Exiting.") if __name__ == '__main__': diff --git a/requirements.txt b/requirements.txt index d57f730..93160de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,12 @@ pbr>=0.6,!=0.7,<1.0 anyjson>=0.3.3 -amqp eventlet>=0.16.1,!=0.17.0 -kombu>=2.5.0 PyMySQL>=0.6.2 # MIT License Pillow==2.4.0 # MIT -python-daemon requests>=2.2.0,!=2.4.0 Flask>=0.10,<1.0 flask-sqlalchemy flask-restless oslo.config>=1.9.3,<1.10.0 # Apache-2.0 oslo.log>=1.0.0,<1.1.0 # Apache-2.0 +oslo.messaging>=1.8.0,<1.9.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 6066463..25bcb66 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,6 @@ setup-hooks = [entry_points] console_scripts = faafo-producer = faafo.producer:main - faafo-tracker = faafo.tracker:main faafo-worker = faafo.worker:main faafo-api = faafo.api:main