# 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 hashlib import io import socket import uuid from pkg_resources import resource_filename import flask from flask import request 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 faafo import queues from faafo import version from libcloud.storage.types import Provider from libcloud.storage.providers import get_driver 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='8080', 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()) # Initialize Swift driver Swift = get_driver(Provider.OPENSTACK_SWIFT) driver = Swift( user='CloudComp2', key='demo', tenant_name='CloudComp2', auth_url='https://10.32.4.29:5000/v3', ) # Ensure container exists try: container = driver.get_container(container_name='fractals') except: # Create container if it doesn't exist container = driver.create_container(container_name='fractals') 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 with app.app_context(): 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) # Stores Swift object name/path 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) generated_by = db.Column(db.String(256), nullable=True) def __repr__(self): return '' % self.uuid with app.app_context(): db.create_all() manager = APIManager(app=app, session=db.session) connection = Connection(CONF.transport_url) def upload_image_to_swift(image_bytes, object_name): """Upload image bytes to Swift storage and return the object name.""" try: LOG.debug(f"Uploading image to Swift: {object_name}") obj = driver.upload_object_via_stream( iterator=io.BytesIO(image_bytes), container=container, object_name=object_name ) LOG.debug(f"Successfully uploaded {object_name} to Swift") return object_name except Exception as e: LOG.error(f"Failed to upload image to Swift: {e}") raise def download_image_from_swift(object_name): """Download image from Swift storage.""" try: LOG.debug(f"Downloading image from Swift: {object_name}") obj = driver.get_object(container_name='fractals', object_name=object_name) stream = driver.download_object_as_stream(obj) image_data = b''.join(stream) LOG.debug(f"Successfully downloaded {object_name} from Swift") return image_data except Exception as e: LOG.error(f"Failed to download image from Swift: {e}") raise @app.route('/', methods=['GET']) @app.route('/index', methods=['GET']) @app.route('/index/', methods=['GET']) def index(page=1): hostname = socket.gethostname() fractals = Fractal.query.filter( (Fractal.checksum is not None) & (Fractal.size is not None)).paginate( page=page, per_page=5) return flask.render_template('index.html', fractals=fractals, hostname=hostname) @app.route('/fractal/', methods=['GET']) def get_fractal(fractalid): fractal = Fractal.query.filter_by(uuid=fractalid).first() if not fractal or not fractal.url: return flask.jsonify({'code': 404, 'message': 'Fractal not found'}), 404 try: image_data = download_image_from_swift(fractal.url) response = flask.make_response(image_data) response.content_type = "image/png" return response except Exception as e: LOG.error(f"Error retrieving fractal {fractalid}: {e}") return flask.jsonify({'code': 500, 'message': 'Error retrieving fractal'}), 500 def generate_fractal(**kwargs): LOG.debug("Postprocessor called!" + str(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 convert_image_to_binary(**kwargs): """Process the image data from worker and upload to Swift.""" LOG.debug("Preprocessor call: " + str(kwargs)) if 'image' in kwargs['data']['data']['attributes']: LOG.debug("Processing image for Swift upload...") # Get the base64 encoded image from worker image_base64 = kwargs['data']['data']['attributes']['image'] image_bytes = base64.b64decode(image_base64) # Generate object name using UUID fractal_uuid = kwargs['data']['data']['attributes']['uuid'] object_name = f"{fractal_uuid}.png" try: # Upload to Swift swift_object_name = upload_image_to_swift(image_bytes, object_name) # Update the fractal record with Swift object name instead of binary data kwargs['data']['data']['attributes']['url'] = swift_object_name # Remove the binary image data since we're storing in Swift del kwargs['data']['data']['attributes']['image'] LOG.debug(f"Image uploaded to Swift as {swift_object_name}") except Exception as e: LOG.error(f"Failed to upload image to Swift: {e}") # Keep the binary data as fallback if Swift upload fails kwargs['data']['data']['attributes']['image'] = \ str(kwargs['data']['data']['attributes']['image']).encode("ascii") def main(): print("Starting API server with Swift storage...") with app.app_context(): manager.create_api(Fractal, methods=['GET', 'POST', 'DELETE', 'PATCH'], postprocessors={'POST_RESOURCE': [generate_fractal]}, preprocessors={'PATCH_RESOURCE': [convert_image_to_binary]}, exclude=['image'], url_prefix='/v1', allow_client_generated_ids=True) app.run(host=CONF.listen_address, port=CONF.bind_port, debug=True)