#!/usr/bin/env python3 """

Swift mock


author: Thomas Duval

mail: thomas.duval@orange.com

version: 0.1

API:

- GET /info List activated capabilities

- GET /v1/{account} Show account details and list containers
- POST /v1/{account} Create, update, or delete account metadata
- HEAD /v1/{account} Show account metadata

- GET /v1/{account}/{container} Show container details and list objects
- PUT /v1/{account}/{container} Create container
- DELETE /v1/{account}/{container} Delete container
- POST /v1/{account}/{container} Create, update, or delete container metadata
- HEAD /v1/{account}/{container} Show container metadata

- GET /v1/{account}/{container}/{object} Get object content and metadata
- PUT /v1/{account}/{container}/{object} Create or replace object
- COPY /v1/{account}/{container}/{object} Copy object
- DELETE /v1/{account}/{container}/{object} Delete object
- COPY /v1/{account}/{container}/{object} Copy object
- HEAD /v1/{account}/{container}/{object} Show object metadata
- POST /v1/{account}/{container}/{object} Create or update object metadata

Locally, the datastore is based on directories in /tmp/swift_dir, those directories are built with the following structure: {data_dir}/{account}/{container}/{object}.

For more information on SWIFT API: api-ref-objectstorage-v1.html

""" import cherrypy import json import os import glob import shutil import time import hashlib TEMPLATE_HTML = """ {title} {body} """ @cherrypy.popargs('account', 'container', 'object') class Swift(object): def __init__(self): if os.path.isdir('/tmp'): self.data_dir = "/tmp/swift_data" else: self.data_dir = "data" cherrypy.log("Data dir set to {}".format(self.data_dir)) try: os.mkdir(self.data_dir) except FileExistsError: cherrypy.log("Data dir already exist") def __get_json(self): cl = cherrypy.request.headers['Content-Length'] rawbody = cherrypy.request.body.read(int(cl)) return json.loads(rawbody.decode("utf-8")) def __get_rawdata(self): cl = cherrypy.request.headers['Content-Length'] rawbody = cherrypy.request.body.read(int(cl)) return rawbody @cherrypy.expose def index(self, account="", container="", object=""): return __doc__ @cherrypy.expose def info(self, account="", container="", object=""): info_dict = { "swift": { "version": "1.11.0" }, "staticweb": {}, "tempurl": {} } return json.dumps(info_dict) @cherrypy.expose def v1(self, account="", container="", object=""): print("v1", account, container, object) if account and not container and not object: if cherrypy.request.method == "GET": return self.show_account(account) elif cherrypy.request.method == "POST": return self.create_account(account) elif cherrypy.request.method == "HEAD": return self.show_account_metadata(account) elif account and container and not object: if cherrypy.request.method == "GET": return self.show_container(account, container) elif cherrypy.request.method == "PUT": return self.create_container(account, container) elif cherrypy.request.method == "DELETE": return self.delete_container(account, container) elif cherrypy.request.method == "POST": return self.modify_container(account, container) elif cherrypy.request.method == "HEAD": return self.show_container_metadata(account, container) elif account and container and object: if cherrypy.request.method == "GET": return self.show_object(account, container, object) elif cherrypy.request.method == "PUT": return self.create_object(account, container, object) elif cherrypy.request.method == "DELETE": return self.delete_object(account, container, object) elif cherrypy.request.method == "COPY": return self.copy_object(account, container, object) elif cherrypy.request.method == "POST": return self.modify_object(account, container, object) elif cherrypy.request.method == "HEAD": return self.show_object_metadata(account, container, object) raise cherrypy.HTTPError(500, "Request not supported ({} - {})".format( cherrypy.request.method, cherrypy.request.query_string)) def show_account(self, account): list_dir = glob.glob(os.path.join(self.data_dir, account, "*")) data_list = list() for _container in list_dir: _objects = glob.glob(os.path.join(self.data_dir, account, _container, "*")) data_list.append({ "count": len(_objects), "bytes": 0, "name": os.path.basename(_container) }) return json.dumps(data_list) def create_account(self, account): if not os.path.isdir(os.path.join(self.data_dir, account)): os.mkdir(os.path.join(self.data_dir, account)) cherrypy.response.status = 204 return "OK" raise cherrypy.HTTPError(500, "Account already created") def show_account_metadata(self, account): if not os.path.isdir(os.path.join(self.data_dir, account)): raise cherrypy.HTTPError(404) # TODO: header raise cherrypy.HTTPError(500, "Not implemented") def show_container(self, account, container): list_dir = glob.glob(os.path.join(self.data_dir, account, container, "*")) data_list = list() for _object in list_dir: data_list.append({ "hash": hashlib.sha256(str.encode(_object)).hexdigest(), "last_modified": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(os.path.getmtime(_object))), "bytes": os.stat(_object).st_size, "name": os.path.basename(_object), "content_type": "application/octet-stream" }) return json.dumps(data_list) def create_container(self, account, container): if not os.path.isdir(os.path.join(self.data_dir, account, container)): os.mkdir(os.path.join(self.data_dir, account, container)) cherrypy.response.status = 204 return raise cherrypy.HTTPError(500, "Container already created") def delete_container(self, account, container): if not os.path.isdir(os.path.join(self.data_dir, account, container)): raise cherrypy.HTTPError(404, "Container not found") try: os.rmdir(os.path.join(self.data_dir, account, container)) except OSError: raise cherrypy.HTTPError(409, "Conflict") cherrypy.response.status = 204 return "OK" # shutil.rmtree((os.path.join(self.data_dir, account))) def modify_container(self, account, container): # TODO: modifications in header return "modify_container {} {}".format(account, container) def show_container_metadata(self, account, container): if os.path.isdir(os.path.join(self.data_dir, account, container)): # TODO: modifications in header return "OK" raise cherrypy.HTTPError(404, "Container not found") def show_object(self, account, container, object): if os.path.isfile(os.path.join(self.data_dir, account, container, object)): # TODO: put metadata in headers return open(os.path.join(self.data_dir, account, container, object), "rb").read() raise cherrypy.HTTPError(404, "Object not found") def create_object(self, account, container, object): if not os.path.isdir(os.path.join(self.data_dir, account)): raise cherrypy.HTTPError(404, "Account not found") if not os.path.isdir(os.path.join(self.data_dir, account, container)): raise cherrypy.HTTPError(404, "Container not found") open(os.path.join(self.data_dir, account, container, object), "wb").write(self.__get_rawdata()) cherrypy.response.status = 201 return def delete_object(self, account, container, object): if not os.path.isfile(os.path.join(self.data_dir, account, container, object)): raise cherrypy.HTTPError(404, "Object not found") os.remove(os.path.join(self.data_dir, account, container, object)) cherrypy.response.status = 204 return def copy_object(self, account, container, object): if not os.path.isfile(os.path.join(self.data_dir, account, container, object)): raise cherrypy.HTTPError(404, "Object not found") # TODO: need to implement headers raise cherrypy.HTTPError(500, "Not implemented") def modify_object(self, account, container, object): # TODO headers raise cherrypy.HTTPError(500, "Not implemented") def show_object_metadata(self, account, container, object): # TODO headers raise cherrypy.HTTPError(500, "Not implemented") application = Swift() cherrypy.config.update({ 'server.socket_host': '127.0.0.1', 'server.socket_port': 8080, }) cherrypy.quickstart(application)