Source code for clustoapi.apps.entity

#!/usr/bin/env python
#
# -*- mode:python; sh-basic-offset:4; indent-tabs-mode:nil; coding:utf-8 -*-
# vim:set tabstop=4 softtabstop=4 expandtab shiftwidth=4 fileencoding=utf-8:
#
# Copyright 2010, Ron Gorodetzky <ron@parktree.net>
# Copyright 2010, Jeremy Grosser <jeremy@synack.me>
# Copyright 2013, Jorge Gallegos <kad@blegh.net>

"""
The ``entity`` application will hold all methods related to entity management
in clusto. That is: creation, querying, modification and overall entity
manipulation.
"""

import bottle
from bottle import request
import clusto
from clustoapi import util


app = bottle.Bottle(autojson=False)
app.config['source_module'] = __name__


@app.get('/')
@app.get('/<driver>')
@app.get('/<driver>/')
[docs]def list(driver=None): """ Returns all entities, or (optionally) all entities of the given driver Example: .. code:: bash $ ${get} ${server_url}/entity/ [ ... ] HTTP: 200 Content-type: application/json Will list all entities Example: .. code:: bash $ ${get} ${server_url}/entity/clustometa [ "/clustometa/clustometa" ] HTTP: 200 Content-type: application/json Will list all entities that match the driver ``clustometa`` The following example should fail because there is no driver ``nondriver``: .. code:: bash $ ${get} ${server_url}/entity/nondriver "The requested driver \"nondriver\" does not exist" HTTP: 412 Content-type: application/json """ result = [] kwargs = {} mode = bottle.request.headers.get('Clusto-Mode', default='compact') headers = {} try: # Assignments are moved into the try block because of the int casting. current = int(bottle.request.headers.get('Clusto-Page', default='0')) per = int(bottle.request.headers.get('Clusto-Per-Page', default='50')) except TypeError as ve: return util.dumps('%s' % (ve,), 400) for param in request.params.keys(): kwargs[param] = request.params.getall(param) if driver: if driver in clusto.driverlist: kwargs['clusto_drivers'] = [clusto.driverlist[driver]] else: return util.dumps('The requested driver "%s" does not exist' % (driver,), 412) ents = clusto.get_entities(**kwargs) if current: ents, total = util.page(ents, current=current, per=per) headers['Clusto-Pages'] = total headers['Clusto-Per-Page'] = per headers['Clusto-Page'] = current for ent in ents: result.append(util.show(ent, mode)) return util.dumps(result, headers=headers)
@app.post('/<driver>')
[docs]def create(driver): """ Creates a new object of the given driver. * Requires HTTP parameters ``name`` Example: .. code:: bash $ ${post} -d 'name=createpool1' ${server_url}/entity/pool [ "/pool/createpool1" ] HTTP: 201 Content-type: application/json Will create a new ``pool1`` object with a ``pool`` driver. If the ``pool1`` object already exists, the status code returned will be 202, and you will see whatever warnings in the ``Warnings`` header: .. code:: bash $ ${post_i} -d 'name=createpool1' ${server_url}/entity/pool HTTP/1.0 202 Accepted ... Warnings: Entity(s) /pool/createpool1 already exist(s)... [ "/pool/createpool1" ] If you try to create a server of an unknown driver, you should receive a 412 status code back: .. code:: bash $ ${post} -d 'name=createobject' ${server_url}/entity/nondriver "Requested driver \"nondriver\" does not exist" HTTP: 412 Content-type: application/json The following example: .. code:: bash $ ${post_i} -d 'name=createpool1' -d 'name=createpool2' ${server_url}/entity/pool HTTP/1.0 202 Accepted ... Warnings: Entity(s) /pool/createpool1 already exist(s)... [ "/pool/createpool1", "/pool/createpool2" ] Will attempt to create new objects ``createpool1`` and ``createpool2`` with a ``pool`` driver. As all objects are validated prior to creation, if any of them already exists the return code will be 202 (Accepted) and you will get an extra header ``Warnings`` with the message. """ if driver not in clusto.driverlist: return util.dumps('Requested driver "%s" does not exist' % (driver,), 412) cls = clusto.driverlist[driver] names = request.params.getall('name') request.params.pop('name') found = [] for name in names: try: found.append(util.unclusto(clusto.get_by_name(name))) except LookupError: pass result = [] for name in names: result.append(util.unclusto(clusto.get_or_create(name, cls))) headers = {} if found: headers['Warnings'] = 'Entity(s) %s already exist(s)' % (','.join(found),) code = 201 if found: code = 202 return util.dumps(result, code, headers=headers)
@app.delete('/<driver>/<name>')
[docs]def delete(driver, name): """ Deletes an object if it matches the given driver * Requires HTTP parameters ``name`` Examples: .. code:: bash $ ${post} -d 'name=servercreated' ${server_url}/entity/basicserver [ "/basicserver/servercreated" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${delete} ${server_url}/entity/nondriver/servercreated "Requested driver \"nondriver\" does not exist" HTTP: 412 Content-type: application/json .. code:: bash $ ${delete} ${server_url}/entity/basicserver/servercreated HTTP: 204 Content-type: .. code:: bash $ ${delete} ${server_url}/entity/basicserver/servercreated HTTP: 404 Content-type: None Will create a new ``servercreated`` object with a ``basicserver`` driver. Then it will proceed to delete it. If the operation succeeded, it will return a 200, if the object doesn't exist, it will return a 404. """ if driver not in clusto.driverlist: return util.dumps('Requested driver "%s" does not exist' % (driver,), 412) notfound = None try: obj = clusto.get_by_name(name) except LookupError: notfound = name code = 204 if notfound: code = 404 else: obj.entity.delete() return bottle.HTTPResponse('', code, headers={'Content-type': None})
@app.get('/<driver>/<name>') @app.get('/<driver>/<name>/')
[docs]def show(driver, name): """ Returns a json representation of the given object Example: .. code:: bash $ ${post} -d 'name=showpool' ${server_url}/entity/pool [ "/pool/showpool" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${get} ${server_url}/entity/pool/showpool { "attrs": [], "contents": [], "driver": "pool", "name": "showpool", "parents": [], "type": "pool" } HTTP: 200 Content-type: application/json Will return a JSON representation of the previously created ``showpool``. .. code:: bash $ ${get} ${server_url}/entity/basicserver/showpool "The driver for object \"showpool\" is not \"basicserver\"" HTTP: 409 Content-type: application/json Will yield a 409 (Conflict) because the object ``showpool`` is not a ``basicserver`` object. """ obj, status, msg = util.get(name, driver) if not obj: return util.dumps(msg, status) return util.dumps(util.show(obj))
@app.post('/<driver>/<name>')
[docs]def action(driver, name): """ Inserts/removes the given device from the request parameters into/from the object Example: .. code:: bash $ ${post} -d 'name=pool1' ${server_url}/entity/pool [ "/pool/pool1" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${post} -d 'name=server1' ${server_url}/entity/basicserver [ "/basicserver/server1" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${post} -d 'device=server1' -d 'action=insert' ${server_url}/entity/pool/pool1 { "attrs": [], "contents": [ "/basicserver/server1" ], "driver": "pool", "name": "pool1", "parents": [], "type": "pool" } HTTP: 200 Content-type: application/json .. code:: bash $ ${post} -d 'device=server1' -d 'action=remove' ${server_url}/entity/pool/pool1 { "attrs": [], "contents": [], "driver": "pool", "name": "pool1", "parents": [], "type": "pool" } HTTP: 200 Content-type: application/json Will: #. Create a pool entity called ``pool1`` #. Create a basicserver entity called ``server1`` #. Insert the entity ``server1`` into the entity ``pool1`` #. Remove the entity ``server1`` from the entity ``pool1`` Examples: .. code:: bash $ ${post} -d 'name=pool2' ${server_url}/entity/pool [ "/pool/pool2" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${post} -d 'name=server2' -d 'name=server3' ${server_url}/entity/basicserver [ "/basicserver/server2", "/basicserver/server3" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${post} -d 'device=server2' -d 'device=server3' -d 'action=insert' ${server_url}/entity/pool/pool2 { "attrs": [], "contents": [ "/basicserver/server2", "/basicserver/server3" ], "driver": "pool", "name": "pool2", "parents": [], "type": "pool" } HTTP: 200 Content-type: application/json .. code:: bash $ ${post} -d 'device=server2' -d 'device=server3' -d 'action=remove' ${server_url}/entity/pool/pool2 { "attrs": [], "contents": [], "driver": "pool", "name": "pool2", "parents": [], "type": "pool" } HTTP: 200 Content-type: application/json The above will: #. Create a pool entity called ``pool2`` #. Create two basicserver entities called ``server2`` and ``server3`` #. Insert both basicserver entities into the pool entity #. Remove both basicserver entities from the pool entity """ obj, status, msg = util.get(name, driver) if not obj: return util.dumps(msg, status) devices = request.params.getall('device') action = request.params.get('action') if not action: bottle.abort(400, 'Parameter \'action\' is required.') devobjs = [] notfound = [] for device in devices: try: devobjs.append(clusto.get_by_name(device)) except LookupError: notfound.append(device) if notfound: bottle.abort(404, 'Objects %s do not exist and cannot be used with "%s"' % (','.join(notfound), name,)) if action == 'insert': for devobj in devobjs: if devobj not in obj: obj.insert(devobj) elif action == 'remove': for devobj in devobjs: if devobj in obj: obj.remove(devobj) else: bottle.abort(400, '%s is not a valid action.' % (action)) return show(driver, name)