diff --git a/faafo/bin/faafo b/faafo/bin/faafo index a12770d..a90f50f 100644 --- a/faafo/bin/faafo +++ b/faafo/bin/faafo @@ -161,7 +161,7 @@ def do_create_fractal(): number = random.randint(int(CONF.command.min_tasks), int(CONF.command.max_tasks)) LOG.info("generating %d task(s)" % number) - for i in xrange(0, number): + for i in range(0, number): task = get_random_task() LOG.debug("created task %s" % task) # NOTE(berendt): only necessary when using requests < 2.4.2 diff --git a/faafo/bin/faafo-worker.bak b/faafo/bin/faafo-worker.bak new file mode 100644 index 0000000..67daf0b --- /dev/null +++ b/faafo/bin/faafo-worker.bak @@ -0,0 +1,52 @@ +#!/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. + +import os +import sys + +import kombu +from oslo_config import cfg +from oslo_log import log + +from faafo.worker import service as worker +from faafo import version + +LOG = log.getLogger('faafo.worker') +CONF = cfg.CONF + +# If ../faafo/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'faafo', '__init__.py')): + sys.path.insert(0, possible_topdir) + +if __name__ == '__main__': + log.register_options(CONF) + log.set_defaults() + + CONF(project='worker', prog='faafo-worker', + default_config_files=['/etc/faafo/faafo.conf'], + version=version.version_info.version_string()) + + log.setup(CONF, 'worker', + version=version.version_info.version_string()) + + connection = kombu.Connection(CONF.transport_url) + server = worker.Worker(connection) + try: + server.run() + except KeyboardInterrupt: + LOG.info("Caught keyboard interrupt. Exiting.") diff --git a/faafo/bin/faafo.bak b/faafo/bin/faafo.bak new file mode 100644 index 0000000..a12770d --- /dev/null +++ b/faafo/bin/faafo.bak @@ -0,0 +1,267 @@ +#!/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. + +import copy +import json +import random +import uuid + +from oslo_config import cfg +from oslo_log import log +from prettytable import PrettyTable +import requests + +from faafo import version + + +LOG = log.getLogger('faafo.client') +CONF = cfg.CONF + + +def get_random_task(): + random.seed() + + if CONF.command.width: + width = int(CONF.command.width) + else: + width = random.randint(int(CONF.command.min_width), + int(CONF.command.max_width)) + + if CONF.command.height: + height = int(CONF.command.height) + else: + height = random.randint(int(CONF.command.min_height), + int(CONF.command.max_height)) + + if CONF.command.iterations: + iterations = int(CONF.command.iterations) + else: + iterations = random.randint(int(CONF.command.min_iterations), + int(CONF.command.max_iterations)) + + if CONF.command.xa: + xa = float(CONF.command.xa) + else: + xa = random.uniform(float(CONF.command.min_xa), + float(CONF.command.max_xa)) + + if CONF.command.xb: + xb = float(CONF.command.xb) + else: + xb = random.uniform(float(CONF.command.min_xb), + float(CONF.command.max_xb)) + + if CONF.command.ya: + ya = float(CONF.command.ya) + else: + ya = random.uniform(float(CONF.command.min_ya), + float(CONF.command.max_ya)) + + if CONF.command.yb: + yb = float(CONF.command.yb) + else: + yb = random.uniform(float(CONF.command.min_yb), + float(CONF.command.max_yb)) + + task = { + 'uuid': str(uuid.uuid4()), + 'width': width, + 'height': height, + 'iterations': iterations, 'xa': xa, + 'xb': xb, + 'ya': ya, + 'yb': yb + } + + return task + + +def do_get_fractal(): + LOG.error("command 'download' not yet implemented") + + +def do_show_fractal(): + LOG.info("showing fractal %s" % CONF.command.uuid) + result = requests.get("%s/v1/fractal/%s" % + (CONF.endpoint_url, CONF.command.uuid)) + if result.status_code == 200: + data = json.loads(result.text) + output = PrettyTable(["Parameter", "Value"]) + output.align["Parameter"] = "l" + output.align["Value"] = "l" + output.add_row(["uuid", data['uuid']]) + output.add_row(["duration", "%f seconds" % data['duration']]) + output.add_row(["dimensions", "%d x %d pixels" % + (data['width'], data['height'])]) + output.add_row(["iterations", data['iterations']]) + output.add_row(["xa", data['xa']]) + output.add_row(["xb", data['xb']]) + output.add_row(["ya", data['ya']]) + output.add_row(["yb", data['yb']]) + output.add_row(["size", "%d bytes" % data['size']]) + output.add_row(["checksum", data['checksum']]) + output.add_row(["generated_by", data['generated_by']]) + print(output) + else: + LOG.error("fractal '%s' not found" % CONF.command.uuid) + + +def do_list_fractals(): + LOG.info("listing all fractals") + + fractals = get_fractals() + output = PrettyTable(["UUID", "Dimensions", "Filesize"]) + for fractal in fractals: + output.add_row([ + fractal["uuid"], + "%d x %d pixels" % (fractal["width"], fractal["height"]), + "%d bytes" % (fractal["size"] or 0), + ]) + print(output) + + +def get_fractals(page=1): + result = requests.get("%s/v1/fractal?page=%d" % + (CONF.endpoint_url, page)) + + fractals = [] + if result.status_code == 200: + data = json.loads(result.text) + if page < data['total_pages']: + fractals = data['objects'] + get_fractals(page + 1) + else: + return data['objects'] + + return fractals + + +def do_delete_fractal(): + LOG.info("deleting fractal %s" % CONF.command.uuid) + result = requests.delete("%s/v1/fractal/%s" % + (CONF.endpoint_url, CONF.command.uuid)) + LOG.debug("result: %s" %result) + + +def do_create_fractal(): + random.seed() + if CONF.command.tasks: + number = int(CONF.command.tasks) + else: + number = random.randint(int(CONF.command.min_tasks), + int(CONF.command.max_tasks)) + LOG.info("generating %d task(s)" % number) + for i in xrange(0, number): + task = get_random_task() + LOG.debug("created task %s" % task) + # NOTE(berendt): only necessary when using requests < 2.4.2 + headers = {'Content-type': 'application/json', + 'Accept': 'text/plain'} + requests.post("%s/v1/fractal" % CONF.endpoint_url, + json.dumps(task), headers=headers) + + +def add_command_parsers(subparsers): + parser = subparsers.add_parser('create') + parser.set_defaults(func=do_create_fractal) + parser.add_argument("--height", default=None, + help="The height of the generate image.") + parser.add_argument("--min-height", default=256, + help="The minimum height of the generate image.") + parser.add_argument("--max-height", default=1024, + help="The maximum height of the generate image.") + parser.add_argument("--width", default=None, + help="The width of the generated image.") + parser.add_argument("--min-width", default=256, + help="The minimum width of the generated image.") + parser.add_argument("--max-width", default=1024, + help="The maximum width of the generated image.") + parser.add_argument("--iterations", default=None, + help="The number of iterations.") + parser.add_argument("--min-iterations", default=128, + help="The minimum number of iterations.") + parser.add_argument("--max-iterations", default=512, + help="The maximum number of iterations.") + parser.add_argument("--tasks", default=None, + help="The number of generated fractals.") + parser.add_argument("--min-tasks", default=1, + help="The minimum number of generated fractals.") + parser.add_argument("--max-tasks", default=10, + help="The maximum number of generated fractals.") + parser.add_argument("--xa", default=None, + help="The value for the parameter 'xa'.") + parser.add_argument("--min-xa", default=-1.0, + help="The minimum value for the parameter 'xa'.") + parser.add_argument("--max-xa", default=-4.0, + help="The maximum value for the parameter 'xa'.") + parser.add_argument("--xb", default=None, + help="The value for the parameter 'xb'.") + parser.add_argument("--min-xb", default=1.0, + help="The minimum value for the parameter 'xb'.") + parser.add_argument("--max-xb", default=4.0, + help="The maximum value for the parameter 'xb'.") + parser.add_argument("--ya", default=None, + help="The value for the parameter 'ya'.") + parser.add_argument("--min-ya", default=-0.5, + help="The minimum value for the parameter 'ya'.") + parser.add_argument("--max-ya", default=-3, + help="The maximum value for the parameter 'ya'.") + parser.add_argument("--yb", default=None, + help="The value for the parameter 'yb'.") + parser.add_argument("--min-yb", default=0.5, + help="The minimum value for the parameter 'yb'.") + parser.add_argument("--max-yb", default=3, + help="The maximum value for the parameter 'yb'.") + + parser = subparsers.add_parser('delete') + parser.set_defaults(func=do_delete_fractal) + parser.add_argument("uuid", help="Fractal to delete.") + + parser = subparsers.add_parser('show') + parser.set_defaults(func=do_show_fractal) + parser.add_argument("uuid", help="Fractal to show.") + + parser = subparsers.add_parser('get') + parser.set_defaults(func=do_get_fractal) + parser.add_argument("uuid", help="Fractal to download.") + + parser = subparsers.add_parser('list') + parser.set_defaults(func=do_list_fractals) + + +client_commands = cfg.SubCommandOpt('command', title='Commands', + help='Show available commands.', + handler=add_command_parsers) + +CONF.register_cli_opts([client_commands]) + +client_cli_opts = [ + cfg.StrOpt('endpoint-url', + default='http://localhost', + help='API connection URL') +] + +CONF.register_cli_opts(client_cli_opts) + + +if __name__ == '__main__': + log.register_options(CONF) + log.set_defaults() + + CONF(project='client', prog='faafo-client', + version=version.version_info.version_string()) + + log.setup(CONF, 'client', + version=version.version_info.version_string()) + + CONF.command.func() diff --git a/faafo/contrib/test_api.py b/faafo/contrib/test_api.py index 559ac3d..6dd9823 100644 --- a/faafo/contrib/test_api.py +++ b/faafo/contrib/test_api.py @@ -34,11 +34,11 @@ assert response.status_code == 201 response = requests.get(url, headers=headers) assert response.status_code == 200 -print(response.json()) +print((response.json())) response = requests.get(url + '/' + uuid, headers=headers) assert response.status_code == 200 -print(response.json()) +print((response.json())) data = { 'checksum': 'c6fef4ef13a577066c2281b53c82ce2c7e94e', @@ -50,7 +50,7 @@ assert response.status_code == 200 response = requests.get(url + '/' + uuid, headers=headers) assert response.status_code == 200 -print(response.json()) +print((response.json())) response = requests.delete(url + '/' + uuid, headers=headers) assert response.status_code == 204 diff --git a/faafo/contrib/test_api.py.bak b/faafo/contrib/test_api.py.bak new file mode 100644 index 0000000..559ac3d --- /dev/null +++ b/faafo/contrib/test_api.py.bak @@ -0,0 +1,56 @@ +#!/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. + +import json +import requests + +url = 'http://127.0.0.1/api/fractal' +headers = {'Content-Type': 'application/json'} + +uuid = '13bf15a8-9f6c-4d59-956f-7d20f7484687' +data = { + 'uuid': uuid, + 'width': 100, + 'height': 100, + 'iterations': 10, + 'xa': 1.0, + 'xb': -1.0, + 'ya': 1.0, + 'yb': -1.0, +} +response = requests.post(url, data=json.dumps(data), headers=headers) +assert response.status_code == 201 + +response = requests.get(url, headers=headers) +assert response.status_code == 200 +print(response.json()) + +response = requests.get(url + '/' + uuid, headers=headers) +assert response.status_code == 200 +print(response.json()) + +data = { + 'checksum': 'c6fef4ef13a577066c2281b53c82ce2c7e94e', + 'duration': 10.12 +} +response = requests.put(url + '/' + uuid, data=json.dumps(data), + headers=headers) +assert response.status_code == 200 + +response = requests.get(url + '/' + uuid, headers=headers) +assert response.status_code == 200 +print(response.json()) + +response = requests.delete(url + '/' + uuid, headers=headers) +assert response.status_code == 204 diff --git a/faafo/doc/source/conf.py b/faafo/doc/source/conf.py index d78328c..83d6994 100644 --- a/faafo/doc/source/conf.py +++ b/faafo/doc/source/conf.py @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -copyright = u'2015, OpenStack contributors' +copyright = '2015, OpenStack contributors' master_doc = 'index' -project = u'First App Application for OpenStack' +project = 'First App Application for OpenStack' source_suffix = '.rst' diff --git a/faafo/doc/source/conf.py.bak b/faafo/doc/source/conf.py.bak new file mode 100644 index 0000000..d78328c --- /dev/null +++ b/faafo/doc/source/conf.py.bak @@ -0,0 +1,17 @@ +# 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. + +copyright = u'2015, OpenStack contributors' +master_doc = 'index' +project = u'First App Application for OpenStack' +source_suffix = '.rst' diff --git a/faafo/faafo/__init__.py.bak b/faafo/faafo/__init__.py.bak new file mode 100644 index 0000000..e69de29 diff --git a/faafo/faafo/api/__init__.py.bak b/faafo/faafo/api/__init__.py.bak new file mode 100644 index 0000000..e69de29 diff --git a/faafo/faafo/api/service.py b/faafo/faafo/api/service.py index a3093c7..0a96f91 100644 --- a/faafo/faafo/api/service.py +++ b/faafo/faafo/api/service.py @@ -12,7 +12,7 @@ import base64 import copy -import cStringIO +import io from pkg_resources import resource_filename import flask @@ -119,8 +119,8 @@ def get_fractal(fractalid): response.status_code = 404 else: image_data = base64.b64decode(fractal.image) - image = Image.open(cStringIO.StringIO(image_data)) - output = cStringIO.StringIO() + image = Image.open(io.StringIO(image_data)) + output = io.StringIO() image.save(output, "PNG") image.seek(0) response = flask.make_response(output.getvalue()) diff --git a/faafo/faafo/api/service.py.bak b/faafo/faafo/api/service.py.bak new file mode 100644 index 0000000..a3093c7 --- /dev/null +++ b/faafo/faafo/api/service.py.bak @@ -0,0 +1,146 @@ +# 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 base64 +import copy +import cStringIO +from pkg_resources import resource_filename + +import flask +from flask_restless import APIManager +from flask_sqlalchemy import SQLAlchemy +from flask_bootstrap import Bootstrap +from kombu import Connection +from kombu.pools import producers +from oslo_config import cfg +from oslo_log import log +from PIL import Image +from sqlalchemy.dialects import mysql + +from faafo import queues +from faafo import version + +LOG = log.getLogger('faafo.api') +CONF = cfg.CONF + +api_opts = [ + cfg.StrOpt('listen-address', + default='0.0.0.0', + help='Listen address.'), + cfg.IntOpt('bind-port', + default='80', + help='Bind port.'), + cfg.StrOpt('database-url', + default='sqlite:////tmp/sqlite.db', + help='Database connection URL.') +] + +CONF.register_opts(api_opts) + +log.register_options(CONF) +log.set_defaults() + +CONF(project='api', prog='faafo-api', + default_config_files=['/etc/faafo/faafo.conf'], + version=version.version_info.version_string()) + +log.setup(CONF, 'api', + version=version.version_info.version_string()) + +template_path = resource_filename(__name__, "templates") +app = flask.Flask('faafo.api', template_folder=template_path) +app.config['DEBUG'] = CONF.debug +app.config['SQLALCHEMY_DATABASE_URI'] = CONF.database_url +db = SQLAlchemy(app) +Bootstrap(app) + + +def list_opts(): + """Entry point for oslo-config-generator.""" + return [(None, copy.deepcopy(api_opts))] + + +class Fractal(db.Model): + uuid = db.Column(db.String(36), primary_key=True) + checksum = db.Column(db.String(256), unique=True) + url = db.Column(db.String(256), nullable=True) + duration = db.Column(db.Float) + size = db.Column(db.Integer, nullable=True) + width = db.Column(db.Integer, nullable=False) + height = db.Column(db.Integer, nullable=False) + iterations = db.Column(db.Integer, nullable=False) + xa = db.Column(db.Float, nullable=False) + xb = db.Column(db.Float, nullable=False) + ya = db.Column(db.Float, nullable=False) + yb = db.Column(db.Float, nullable=False) + + if CONF.database_url.startswith('mysql'): + LOG.debug('Using MySQL database backend') + image = db.Column(mysql.MEDIUMBLOB, nullable=True) + else: + image = db.Column(db.LargeBinary, nullable=True) + + generated_by = db.Column(db.String(256), nullable=True) + + def __repr__(self): + return '' % self.uuid + + +db.create_all() +manager = APIManager(app, flask_sqlalchemy_db=db) +connection = Connection(CONF.transport_url) + + +@app.route('/', methods=['GET']) +@app.route('/index', methods=['GET']) +@app.route('/index/', methods=['GET']) +def index(page=1): + fractals = Fractal.query.filter( + (Fractal.checksum != None) & (Fractal.size != None)).paginate( # noqa + page, 5, error_out=False) + return flask.render_template('index.html', fractals=fractals) + + +@app.route('/fractal/', methods=['GET']) +def get_fractal(fractalid): + fractal = Fractal.query.filter_by(uuid=fractalid).first() + if not fractal: + response = flask.jsonify({'code': 404, + 'message': 'Fracal not found'}) + response.status_code = 404 + else: + image_data = base64.b64decode(fractal.image) + image = Image.open(cStringIO.StringIO(image_data)) + output = cStringIO.StringIO() + image.save(output, "PNG") + image.seek(0) + response = flask.make_response(output.getvalue()) + response.content_type = "image/png" + + return response + + +def generate_fractal(**kwargs): + with producers[connection].acquire(block=True) as producer: + producer.publish(kwargs['result'], + serializer='json', + exchange=queues.task_exchange, + declare=[queues.task_exchange], + routing_key='normal') + + +def main(): + manager.create_api(Fractal, methods=['GET', 'POST', 'DELETE', 'PUT'], + postprocessors={'POST': [generate_fractal]}, + exclude_columns=['image'], + url_prefix='/v1') + app.run(host=CONF.listen_address, port=CONF.bind_port) diff --git a/faafo/faafo/queues.py.bak b/faafo/faafo/queues.py.bak new file mode 100644 index 0000000..4e5a6fd --- /dev/null +++ b/faafo/faafo/queues.py.bak @@ -0,0 +1,32 @@ +# 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 copy + +import kombu +from oslo_config import cfg + +task_exchange = kombu.Exchange('tasks', type='direct') +task_queue = kombu.Queue('normal', task_exchange, routing_key='normal') + +queues_opts = [ + cfg.StrOpt('transport-url', + default='amqp://guest:guest@localhost:5672//', + help='AMQP connection URL.') +] + +cfg.CONF.register_opts(queues_opts) + + +def list_opts(): + """Entry point for oslo-config-generator.""" + return [(None, copy.deepcopy(queues_opts))] diff --git a/faafo/faafo/version.py.bak b/faafo/faafo/version.py.bak new file mode 100644 index 0000000..7a68690 --- /dev/null +++ b/faafo/faafo/version.py.bak @@ -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('faafo') diff --git a/faafo/faafo/worker/__init__.py.bak b/faafo/faafo/worker/__init__.py.bak new file mode 100644 index 0000000..e69de29 diff --git a/faafo/setup.py.bak b/faafo/setup.py.bak new file mode 100644 index 0000000..ee06f22 --- /dev/null +++ b/faafo/setup.py.bak @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# 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 setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr'], + pbr=True)