Use oslo.{config,log}

Replace argparse with oslo.config.
Replace logging with oslo.log.
This commit is contained in:
Christian Berendt 2015-03-06 20:57:37 +01:00
parent 76153ece70
commit 85a090649d
12 changed files with 234 additions and 203 deletions

View File

@ -115,6 +115,8 @@ 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)
@ -125,6 +127,8 @@ Producer service
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
@ -134,6 +138,8 @@ Tracker service
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

View File

@ -1,4 +1,5 @@
#!/bin/sh
oat-api \
--database-url mysql://tutorial:secretsecret@service:3306/tutorial
--database-url mysql://tutorial:secretsecret@service:3306/tutorial \
--debug --verbose

View File

@ -2,4 +2,5 @@
oat-producer \
--amqp-url amqp://tutorial:secretsecret@service:5672/ \
--api-url http://api:5000
--api-url http://api:5000 \
--debug --verbose

View File

@ -2,4 +2,5 @@
oat-tracker \
--amqp-url amqp://tutorial:secretsecret@service:5672/ \
--api-url http://api:5000
--api-url http://api:5000 \
--debug --verbose

View File

@ -2,4 +2,5 @@
oat-worker \
--amqp-url amqp://tutorial:secretsecret@service:5672/ \
--target /home/vagrant
--target /home/vagrant \
--debug --verbose

View File

@ -1,17 +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.
import pbr.version
name = 'openstack-application-tutorial'
__version__ = pbr.version.VersionInfo(name).version_string()

View File

@ -12,36 +12,41 @@
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import json
import logging
import sys
import flask
from flask.ext.sqlalchemy import SQLAlchemy
from oslo_config import cfg
from oslo_log import log
from openstack_application_tutorial import version
def initialize_logging(filename):
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO,
filename=filename)
CONF = cfg.CONF
cli_opts = [
cfg.StrOpt('listen-address',
default='0.0.0.0',
help='Listen address.'),
cfg.StrOpt('database-url',
default='sqlite:////tmp/oat.db',
help='Database connection URL.')
]
def parse_command_line_arguments():
"""Parse the command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument(
"--database-url", type=str, help="database connection URL",
default="sqlite:////tmp/oat.db")
parser.add_argument(
"--log-file", type=str, help="write logs to this file", default=None)
parser.add_argument(
"--daemonize", action="store_true", help="run in background")
return parser.parse_args()
CONF.register_cli_opts(cli_opts)
log.register_options(CONF)
log.set_defaults()
log.setup(CONF, 'api', version=version.version_info.version_string())
CONF(args=sys.argv[1:],
project='api',
version=version.version_info.version_string())
LOG = log.getLogger(__name__)
app = flask.Flask(__name__)
args = parse_command_line_arguments()
initialize_logging(args.log_file)
app.config['SQLALCHEMY_DATABASE_URI'] = args.database_url
app.config['SQLALCHEMY_DATABASE_URI'] = CONF.database_url
db = SQLAlchemy(app)
@ -81,7 +86,7 @@ def publish_result(fractal_id):
if (not flask.request.json or
not flask.request.json.viewkeys() & {
'duration', 'checksum'}):
logging.error("wrong request: %s" % flask.request.json)
LOG.error("wrong request: %s" % flask.request.json)
flask.abort(400)
fractal = get_fractal_from_database(fractal_id)
@ -107,7 +112,7 @@ def create_fractal():
'xa', 'xb', 'ya', 'yb', 'iterations'} or
not flask.request.json['dimension'].viewkeys() >= {
'width', 'height'}):
logging.error("wrong request: %s" % flask.request.json)
LOG.error("wrong request: %s" % flask.request.json)
flask.abort(400)
try:
@ -151,8 +156,8 @@ def not_found(error):
def main():
db.create_all()
app.run(host="0.0.0.0", debug=True)
app.run(host=CONF.listen_address, debug=CONF.debug)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@ -12,9 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import json
import logging
import random
import sys
import time
@ -23,80 +21,93 @@ import uuid
import daemon
import kombu
from kombu.pools import producers
from oslo_config import cfg
from oslo_log import log
import requests
from openstack_application_tutorial import queues
from openstack_application_tutorial import version
def initialize_logging(filename):
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO,
filename=filename)
CONF = cfg.CONF
cli_opts = [
cfg.BoolOpt('daemonize',
default=False,
help='Run in background.'),
cfg.StrOpt('amqp-url',
default='amqp://tutorial:secretsecret@localhost:5672/',
help='AMQP connection URL'),
cfg.StrOpt('api-url',
default='http://localhost:5000',
help='API connection URL')
]
producer_opts = [
cfg.BoolOpt("one-shot", default=False,
help="Generate one set of tasks and exit."),
cfg.IntOpt("max-height", default=1024,
help="The maximum height of the generate image."),
cfg.IntOpt("max-width", default=1024,
help="The maximum width of the generated image."),
cfg.FloatOpt("max-xa", default=-4.0,
help="The maximum value for the parameter 'xa'."),
cfg.FloatOpt("max-xb", default=4.0,
help="The maximum value for the parameter 'xb'."),
cfg.FloatOpt("max-ya", default=-3,
help="The maximum value for the parameter 'ya'."),
cfg.FloatOpt("max-yb", default=3,
help="The maximum value for the parameter 'yb'."),
cfg.IntOpt("max-iterations", default=512,
help="The maximum number of iterations."),
cfg.IntOpt("min-height", default=256,
help="The minimum height of the generate image."),
cfg.IntOpt("min-width", default=256,
help="The minimum width of the generated image."),
cfg.FloatOpt("min-xa", default=-1.0,
help="The minimum value for the parameter 'xa'."),
cfg.FloatOpt("min-xb", default=1.0,
help="The minimum value for the parameter 'xb'."),
cfg.FloatOpt("min-ya", default=-0.5,
help="The minimum value for the parameter 'ya'."),
cfg.FloatOpt("min-yb", default=0.5,
help="The minimum value for the parameter 'yb'."),
cfg.IntOpt("min-iterations", default=128,
help="The minimum number of iterations."),
cfg.FloatOpt("min-pause", default=1.0,
help="The minimum pause in seconds."),
cfg.FloatOpt("max-pause", default=10.0,
help="The maximum pause in seconds."),
cfg.IntOpt("min-tasks", default=1,
help="The minimum number of generated tasks."),
cfg.IntOpt("max-tasks", default=10,
help="The maximum number of generated tasks.")
]
CONF.register_cli_opts(cli_opts)
CONF.register_cli_opts(producer_opts)
log.register_options(CONF)
log.setup(CONF, 'producer', version=version.version_info.version_string())
log.set_defaults()
CONF(args=sys.argv[1:],
project='producer',
version=version.version_info.version_string())
LOG = log.getLogger(__name__)
def parse_command_line_arguments():
"""Parse the command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument(
"--amqp-url", type=str, help="AMQP connection URL",
default="amqp://tutorial:secretsecret@localhost:5672/")
parser.add_argument(
"--api-url", type=str, help="API connection URL",
default="http://localhost:5000")
parser.add_argument(
"--log-file", type=str, help="write logs to this file", default=None)
parser.add_argument(
"--daemonize", action="store_true", help="run in background")
parser.add_argument("--one-shot", action='store_true',
help="Generate one set of tasks and exit.")
parser.add_argument("--max-height", type=int, default=1024,
help="The maximum height of the generate image.")
parser.add_argument("--max-width", type=int, default=1024,
help="The maximum width of the generated image.")
parser.add_argument("--max-xa", type=float, default=-4.0,
help="The maximum value for the parameter 'xa'.")
parser.add_argument("--max-xb", type=float, default=4.0,
help="The maximum value for the parameter 'xb'.")
parser.add_argument("--max-ya", type=float, default=-3,
help="The maximum value for the parameter 'ya'.")
parser.add_argument("--max-yb", type=float, default=3,
help="The maximum value for the parameter 'yb'.")
parser.add_argument("--max-iterations", type=int, default=512,
help="The maximum number of iterations.")
parser.add_argument("--min-height", type=int, default=256,
help="The minimum height of the generate image.")
parser.add_argument("--min-width", type=int, default=256,
help="The minimum width of the generated image.")
parser.add_argument("--min-xa", type=float, default=-1.0,
help="The minimum value for the parameter 'xa'.")
parser.add_argument("--min-xb", type=float, default=1.0,
help="The minimum value for the parameter 'xb'.")
parser.add_argument("--min-ya", type=float, default=-0.5,
help="The minimum value for the parameter 'ya'.")
parser.add_argument("--min-yb", type=float, default=0.5,
help="The minimum value for the parameter 'yb'.")
parser.add_argument("--min-iterations", type=int, default=128,
help="The minimum number of iterations.")
parser.add_argument("--min-pause", type=float, default=1.0,
help="The minimum pause in seconds.")
parser.add_argument("--max-pause", type=float, default=10.0,
help="The maximum pause in seconds.")
parser.add_argument("--min-tasks", type=int, default=1,
help="The minimum number of generated tasks.")
parser.add_argument("--max-tasks", type=int, default=10,
help="The maximum number of generated tasks.")
return parser.parse_args()
def generate_task(args):
def generate_task():
random.seed()
width = random.randint(args.min_width, args.max_width)
height = random.randint(args.min_height, args.max_height)
iterations = random.randint(args.min_iterations, args.max_iterations)
xa = random.uniform(args.min_xa, args.max_xa)
xb = random.uniform(args.min_xb, args.max_xb)
ya = random.uniform(args.min_ya, args.max_ya)
yb = random.uniform(args.min_yb, args.max_yb)
width = random.randint(CONF.min_width, CONF.max_width)
height = random.randint(CONF.min_height, CONF.max_height)
iterations = random.randint(CONF.min_iterations, CONF.max_iterations)
xa = random.uniform(CONF.min_xa, CONF.max_xa)
xb = random.uniform(CONF.min_xb, CONF.max_xb)
ya = random.uniform(CONF.min_ya, CONF.max_ya)
yb = random.uniform(CONF.min_yb, CONF.max_yb)
task = {
'uuid': str(uuid.uuid4()),
@ -116,19 +127,19 @@ def generate_task(args):
return task
def run(args, messaging, api_url):
def run(messaging, api_url):
while True:
random.seed()
number = random.randint(args.min_tasks, args.max_tasks)
logging.info("generating %d task(s)" % number)
number = random.randint(CONF.min_tasks, CONF.max_tasks)
LOG.info("generating %d task(s)" % number)
for i in xrange(0, number):
task = generate_task(args)
task = generate_task()
# NOTE(berendt): only necessary when using requests < 2.4.2
headers = {'Content-type': 'application/json',
'Accept': 'text/plain'}
requests.post("%s/v1/fractals" % api_url, json.dumps(task),
headers=headers)
logging.info("generated task: %s" % task)
LOG.info("generated task: %s" % task)
with producers[messaging].acquire(block=True) as producer:
producer.publish(
task,
@ -137,29 +148,26 @@ def run(args, messaging, api_url):
declare=[queues.task_exchange],
routing_key='tasks')
if args.one_shot:
if CONF.one_shot:
break
pause = random.uniform(args.min_pause, args.max_pause)
logging.info("sleeping for %f seconds" % pause)
pause = random.uniform(CONF.min_pause, CONF.max_pause)
LOG.info("sleeping for %f seconds" % pause)
time.sleep(pause)
def main():
args = parse_command_line_arguments()
initialize_logging(args.log_file)
messaging = kombu.Connection(args.amqp_url)
messaging = kombu.Connection(CONF.amqp_url)
if args.daemonize:
if CONF.daemonize:
with daemon.DaemonContext():
run(args, messaging, args.api_url)
run(messaging, CONF.api_url)
else:
try:
run(args, messaging, args.api_url)
except KeyboardInterrupt:
return 0
run(messaging, CONF.api_url)
except Exception as e:
sys.exit("ERROR: %s" % e)
return 0
if __name__ == '__main__':
sys.exit(main())
main()

View File

@ -14,17 +14,45 @@
# based on http://code.activestate.com/recipes/577120-julia-fractals/
import argparse
import json
import logging
import sys
import daemon
import kombu
from kombu.mixins import ConsumerMixin
from oslo_config import cfg
from oslo_log import log
import requests
from openstack_application_tutorial import queues
from openstack_application_tutorial import version
CONF = cfg.CONF
cli_opts = [
cfg.BoolOpt('daemonize',
default=False,
help='Run in background.'),
cfg.StrOpt('amqp-url',
default='amqp://tutorial:secretsecret@localhost:5672/',
help='AMQP connection URL'),
cfg.StrOpt('api-url',
default='http://localhost:5000',
help='API connection URL')
]
CONF.register_cli_opts(cli_opts)
log.register_options(CONF)
log.set_defaults(default_log_levels=[])
log.setup(CONF, 'tracker', version=version.version_info.version_string())
CONF(args=sys.argv[1:],
project='tracker',
version=version.version_info.version_string())
LOG = log.getLogger(__name__)
class Tracker(ConsumerMixin):
@ -39,9 +67,9 @@ class Tracker(ConsumerMixin):
callbacks=[self.process_result])]
def process_result(self, body, message):
logging.info("processing result %s" % body['uuid'])
logging.info("elapsed time %f seconds" % body['duration'])
logging.info("checksum %s" % body['checksum'])
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'])
@ -55,42 +83,19 @@ class Tracker(ConsumerMixin):
message.ack()
def initialize_logging(filename):
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO,
filename=filename)
def parse_command_line_arguments():
"""Parse the command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument(
"--amqp-url", type=str, help="AMQP connection URL",
default="amqp://tutorial:secretsecret@localhost:5672/")
parser.add_argument(
"--api-url", type=str, help="API connection URL",
default="http://localhost:5000")
parser.add_argument(
"--log-file", type=str, help="write logs to this file", default=None)
parser.add_argument(
"--daemonize", action="store_true", help="run in background")
return parser.parse_args()
def main():
args = parse_command_line_arguments()
initialize_logging(args.log_file)
tracker = Tracker(args.amqp_url, args.api_url)
LOG.info("XXX")
if args.daemonize:
tracker = Tracker(CONF.amqp_url, CONF.api_url)
if CONF.daemonize:
with daemon.DaemonContext():
tracker.run()
else:
try:
tracker.run()
except KeyboardInterrupt:
return 0
return 0
except Exception as e:
sys.exit("ERROR: %s" % e)
if __name__ == '__main__':
sys.exit(main())
main()

View File

@ -0,0 +1,15 @@
# 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.
import pbr.version
version_info = pbr.version.VersionInfo('openstack-application-tutorial')

View File

@ -14,9 +14,7 @@
# based on http://code.activestate.com/recipes/577120-julia-fractals/
import argparse
import hashlib
import logging
import os
from PIL import Image
import random
@ -27,8 +25,38 @@ import daemon
import kombu
from kombu.mixins import ConsumerMixin
from kombu.pools import producers
from oslo_config import cfg
from oslo_log import log
from openstack_application_tutorial import queues
from openstack_application_tutorial import version
CONF = cfg.CONF
cli_opts = [
cfg.BoolOpt('daemonize',
default=False,
help='Run in background.'),
cfg.StrOpt('target',
default='/tmp',
help='Target directory for fractal image files.'),
cfg.StrOpt('amqp-url',
default='amqp://tutorial:secretsecret@localhost:5672/',
help='AMQP connection URL')
]
CONF.register_cli_opts(cli_opts)
log.register_options(CONF)
log.setup(CONF, 'worker', version=version.version_info.version_string())
log.set_defaults()
CONF(args=sys.argv[1:],
project='worker',
version=version.version_info.version_string())
LOG = log.getLogger(__name__)
class JuliaSet(object):
@ -91,8 +119,8 @@ class Worker(ConsumerMixin):
callbacks=[self.process_task])]
def process_task(self, body, message):
logging.info("processing task %s" % body['uuid'])
logging.debug(body)
LOG.info("processing task %s" % body['uuid'])
LOG.debug(body)
start_time = time.time()
juliaset = JuliaSet(body['dimension']['width'],
body['dimension']['height'],
@ -103,18 +131,18 @@ class Worker(ConsumerMixin):
body['parameter']['iterations'])
filename = os.path.join(self.target, "%s.png" % body['uuid'])
elapsed_time = time.time() - start_time
logging.info("task %s processed in %f seconds" %
(body['uuid'], elapsed_time))
LOG.info("task %s processed in %f seconds" %
(body['uuid'], elapsed_time))
juliaset.save(filename)
logging.info("saved result of task %s to file %s" %
(body['uuid'], filename))
LOG.info("saved result of task %s to file %s" %
(body['uuid'], filename))
checksum = hashlib.sha256(open(filename, 'rb').read()).hexdigest()
result = {
'uuid': body['uuid'],
'duration': elapsed_time,
'checksum': checksum
}
logging.info("pushed result: %s" % result)
LOG.info("pushed result: %s" % result)
with producers[self.connection].acquire(block=True) as producer:
producer.publish(result, serializer='pickle',
exchange=queues.result_exchange,
@ -124,42 +152,18 @@ class Worker(ConsumerMixin):
message.ack()
def initialize_logging(filename):
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO,
filename=filename)
def parse_command_line_arguments():
"""Parse the command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument(
"--target", type=str, help="Target directory",
default="/tmp")
parser.add_argument(
"--amqp-url", type=str, help="AMQP connection URL",
default="amqp://tutorial:secretsecret@localhost:5672/")
parser.add_argument(
"--log-file", type=str, help="write logs to this file", default=None)
parser.add_argument(
"--daemonize", action="store_true", help="run in background")
return parser.parse_args()
def main():
args = parse_command_line_arguments()
initialize_logging(args.log_file)
worker = Worker(args.amqp_url, args.target)
worker = Worker(CONF.amqp_url, CONF.target)
if args.daemonize:
if CONF.daemonize:
with daemon.DaemonContext():
worker.run()
else:
try:
worker.run()
except KeyboardInterrupt:
return 0
except Exception as e:
sys.exit("ERROR: %s" % e)
return 0
if __name__ == '__main__':
sys.exit(main())
main()

View File

@ -1,7 +1,6 @@
pbr>=0.6,!=0.7,<1.0
anyjson
amqp
argparse
kombu
mysql
pillow
@ -9,3 +8,5 @@ python-daemon
requests
flask
flask-sqlalchemy
oslo.config
oslo.log