files/default/rgw_webservice.py (166 lines of code) (raw):

#!/usr/bin/env python # # Author: Hans Chris Jones <chris.jones@lambdastack.io> # Copyright 2018, LambdaStack # # 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. # # NB: This file is used as a RGW WebService that can be used with Apache or NGINX or anything that supports uwsgi. # The following implement this service. Chef-BCS was modeled after Cepheus' implementation which uses NGINX: # 1. https://github.com/bloomberg/chef-bcs # 2. https://github.com/cepheus-io/cepheus import logging import logging.handlers import subprocess import json import os import datetime import flask from flask import request # NB: Setup Logging log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) # handler = logging.handlers.SysLogHandler(address = '/dev/log') handler = logging.handlers.TimedRotatingFileHandler('/var/log/rgw_webservice/rgw_webservice.log', when='midnight', backupCount=5) formatter = logging.Formatter('%(module)s.%(funcName)s: %(message)s') handler.setFormatter(formatter) log.addHandler(handler) app = flask.Flask(__name__) # NB: Use flask.jsonify in the methods/functions that return json and not globally class RGWWebServiceAPI(object): def __init__(self): # Setup admin user info here pass def user_create(self, user, display_name=None, remote_addr=None, region=None, zone=None, access_key=None, secret_key=None, email=None, zone_region_prefix="client.radosgw"): # Set the display_name equal to the user id if display_name not passed in! if display_name is None: display_name = user if region is not None and zone is not None: cmd = ["sudo", "/bin/radosgw-admin", "user", "create", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--display-name", "%s" % display_name] else: cmd = ["/usr/bin/radosgw-admin", "user", "create", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--display-name", "%s" % display_name] if region is not None and zone is not None: cmd.append("-n") # NB: This should match '[client.radosgw...]' or something similar found in ceph.conf for the RGW section cmd.append("%s.%s-%s" % (zone_region_prefix, region, zone)) if email is not None: cmd.append("--email") cmd.append("%s" % email) if access_key is not None: cmd.append("--access-key") cmd.append("%s" % access_key) if secret_key is not None: # Newer versions of radosgw-admin support --secret-key too cmd.append("--secret") cmd.append("%s" % secret_key) return call(cmd, remote_addr) def user_get(self, user, region=None, zone=None, zone_region_prefix="client.radosgw"): if region is not None and zone is not None: cmd = ["sudo", "/bin/radosgw-admin", "user", "info", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user] else: cmd = ["/usr/bin/radosgw-admin", "user", "info", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user] if region is not None and zone is not None: cmd.append("-n") # NB: This should match '[client.radosgw...]' or something similar found in ceph.conf for the RGW section cmd.append("%s.%s-%s" % (zone_region_prefix, region, zone)) return call(cmd) def user_keys_add(self, user, access_key=None, secret_key=None, region=None, zone=None, zone_region_prefix="client.radosgw"): if region is not None and zone is not None: cmd = ["sudo", "/usr/bin/radosgw-admin", "key", "create", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--key-type", "s3"] else: cmd = ["/usr/bin/radosgw-admin", "key", "create", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--key-type", "s3"] if access_key is not None: cmd.append("--access_key") cmd.append("%s" % access_key) else: cmd.append("--gen-access-key") if secret_key is not None: cmd.append("--secret") cmd.append("%s" % secret) else: cmd.append("--gen-secret") if region is not None and zone is not None: cmd.append("-n") # NB: This should match '[client.radosgw...]' or something similar found in ceph.conf for the RGW section cmd.append("%s.%s-%s" % (zone_region_prefix, region, zone)) return call(cmd) def user_quota_enable(self, user, region=None, zone=None, zone_region_prefix="client.radosgw"): if region is not None and zone is not None: cmd = ["sudo", "/bin/radosgw-admin", "quota", "enable", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--quota-scope", "user"] else: cmd = ["/usr/bin/radosgw-admin", "quota", "enable", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--quota-scope", "user"] if region is not None and zone is not None: cmd.append("-n") # NB: This should match '[client.radosgw...]' or something similar found in ceph.conf for the RGW section cmd.append("%s.%s-%s" % (zone_region_prefix, region, zone)) return call(cmd) def user_quota_disable(self, user, region=None, zone=None, zone_region_prefix="client.radosgw"): if region is not None and zone is not None: cmd = ["sudo", "/bin/radosgw-admin", "quota", "disable", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--quota-scope", "user"] else: cmd = ["/usr/bin/radosgw-admin", "quota", "disable", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--quota-scope", "user"] if region is not None and zone is not None: cmd.append("-n") # NB: This should match '[client.radosgw...]' or something similar found in ceph.conf for the RGW section cmd.append("%s.%s-%s" % (zone_region_prefix, region, zone)) return call(cmd) def user_quota_set(self, user, num, scope="user", qtype="size", region=None, zone=None, zone_region_prefix="client.radosgw"): if region is not None and zone is not None: cmd = ["sudo", "/bin/radosgw-admin", "quota", "set", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--quota-scope", "%s" % scope] else: cmd = ["/usr/bin/radosgw-admin", "quota", "set", "--conf", "/etc/ceph/ceph.conf", "--uid", "%s" % user, "--quota-scope", "%s" % scope] if qtype == "objects": cmd.append("--max-objects") else: cmd.append("--max-size") cmd.append("%s" % num) if region is not None and zone is not None: cmd.append("-n") # NB: This should match '[client.radosgw...]' or something similar found in ceph.conf for the RGW section cmd.append("%s.%s-%s" % (zone_region_prefix, region, zone)) return call(cmd) # NB: Expects JSON returned def call(cmd, remote_addr=None): if remote_addr is None: remote_addr = '' log.debug(str(datetime.datetime.utcnow()) + ' ' + remote_addr + ' ' + ' '.join([str(x) for x in cmd])) process = subprocess.Popen(cmd, env=os.environ.copy(), stdout=subprocess.PIPE) json_output, err = process.communicate() if err: log.error(err) return None # log.debug(json_output) return json_output def flaskify(func, *args, **kwargs): result = '' """ Wraps Flask response generation so that the underlying worker functions can be invoked without a Flask application context. :param func: function to invoke :param *args: any arguments to pass to the func :param **kwargs: any keyword arguments to pass to func :returns: Flask response object """ try: result = func(*args, **kwargs) except Exception, e: log.error(e) return result @app.route('/') def help(): return flask.render_template('rgw_webservice_help.html') @app.route('/v1/users/create/<user>', methods=['PUT']) def rgw_users_create(user): api = RGWWebServiceAPI() # Getting parameters # NB: Display Name is required display_name = request.args.get('display_name') region = request.args.get('region') zone = request.args.get('zone') access_key = request.args.get('access_key') secret_key = request.args.get('secret_key') email = request.args.get('email') remote_addr = request.headers.get('X-Forwarded-For') if remote_addr is None: remote_addr = request.remote_addr # Json example # flask.jsonify(data_dict) return flaskify(api.user_create, user, display_name=display_name, remote_addr=remote_addr, region=region, zone=zone, access_key=access_key, secret_key=secret_key, email=email) @app.route('/v1/users/get/<user>', methods=['GET']) def rgw_users_get(user): api = RGWWebServiceAPI() # Getting parameters region = request.args.get('region') zone = request.args.get('zone') return flaskify(api.user_get, user, region=region, zone=zone) @app.route('/v1/users/keys/add/<user>', methods=['PUT']) def rgw_users_keys_add(user): api = RGWWebServiceAPI() # Getting parameters access_key = request.args.get('access_key') secret_key = request.args.get('secret_key') region = request.args.get('region') zone = request.args.get('zone') # Json example # flask.jsonify(data_dict) return flaskify(api.user_keys_add, user, access_key=access_key, secret_key=secret_key, region=region, zone=zone) @app.route('/v1/users/quota/enable/<user>', methods=['PUT']) def rgw_users_quota_enable(user): api = RGWWebServiceAPI() # Getting parameters region = request.args.get('region') zone = request.args.get('zone') # Json example # flask.jsonify(data_dict) return flaskify(api.user_quota_enable, user, region=region, zone=zone) @app.route('/v1/users/quota/disable/<user>', methods=['PUT']) def rgw_users_quota_disable(user): api = RGWWebServiceAPI() # Getting parameters region = request.args.get('region') zone = request.args.get('zone') # Json example # flask.jsonify(data_dict) return flaskify(api.user_quota_disable, user, region=region, zone=zone) # NB: scope can be 'user' or 'bucket' # NB: qtype can be 'objects' or 'size' @app.route('/v1/users/quota/set/<user>/<scope>/<qtype>', methods=['PUT']) def rgw_users_quota_set(user, scope, qtype): api = RGWWebServiceAPI() # Getting parameters num = request.args.get('num') region = request.args.get('region') zone = request.args.get('zone') # Json example # flask.jsonify(data_dict) return flaskify(api.user_quota_set, user, num, scope, qtype, region=region, zone=zone) if __name__ == '__main__': app.run()