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})
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))
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})
def _write_attrs(method, name, **kwargs): """ Helper method for reduced code between POST and PUT. Returns a response for the methods calling it. """ if method == 'set': code = 200 if method == 'add': code = 201 else: util.dumps('"%s" is neither set nor add. How did you get here?' % method, 400) request_kwargs = dict(request.params.items()) driver = kwargs.get('driver', None) obj, status, msg = util.get(name, driver) if not obj: return util.dumps(msg, status) try: # Merge URL values and kwarg values, but do not allow conflicts. for k, v in request_kwargs.items(): if kwargs.get(k) is not None and kwargs[k] != v: raise ValueError('Two different values were submitted for "%s": %s' % (k, [kwargs[k], v])) kwargs[k] = v # Additionally capture a value error if the json is bad. json_kwargs = request.json except ValueError as ve: return util.dumps('%s' % (ve,), 400) if json_kwargs: if request.query: return util.dumps('Error: json and query params may not be passed in the same request.', 400) kwargs = json_kwargs # Adds support for bulk attr posting. attrs = [kwargs] if isinstance(kwargs, dict) else kwargs # Check for malformed data or missing pieces before adding any attrs. for attr in attrs: for k in ('key', 'value'): if k not in attr.keys(): bottle.abort(412, 'Provide at least "key" and "value"') if 'number' in attr: try: attr['number'] = int(attr['number']) except ValueError as ve: return util.dumps('%s' % (ve,), 400) if 'datatype' in attr: datatype = attr.pop('datatype') mask = attr.pop('mask', '%Y-%m-%dT%H:%M:%S.%f') attr['value'] = util.typecast(attr['value'], datatype, mask=mask) for attr in attrs: getattr(obj, method + '_attr')(**attr) return util.dumps([util.unclusto(_) for _ in obj.attrs()], code)
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)
def attrs(name, key=None, subkey=None, number=None): """ Query attributes from this object. Example: .. code:: bash $ ${post} -d 'name=attrpool1' ${server_url}/entity/pool [ "/pool/attrpool1" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${get} ${server_url}/attribute/attrpool1 [] HTTP: 200 Content-type: application/json Will show all the attributes from the object ``attrpool1``: .. code:: bash $ ${get} -d 'driver=pool' ${server_url}/attribute/attrpool1 [] HTTP: 200 Content-type: application/json Will show all the attributes from the object ``attrpool1`` **if** the driver for ``attrpool1`` is ``pool``. In the same vein this code: .. code:: bash $ ${get} -d 'driver=basicserver' ${server_url}/attribute/attrpool1 ... HTTP: 409 ... Should fail, because the ``attrpool1`` object is of type ``pool``, **not** ``basicserver`` Example: .. code:: bash $ ${get} ${server_url}/attribute/attrpool1/owner [] HTTP: 200 Content-type: application/json Will show the attributes for ``server1`` if their key is ``owner``. """ attrs = [] kwargs = dict(request.params.items()) driver = kwargs.get('driver', None) obj, status, msg = util.get(name, driver) if not obj: return util.dumps(msg, status) qkwargs = {} if key: qkwargs['key'] = key if subkey: qkwargs['subkey'] = subkey if number: qkwargs['number'] = number for attr in obj.attrs(**qkwargs): attrs.append(util.unclusto(attr)) return util.dumps(attrs)
def del_attrs(name, key, subkey=None, number=None): """ Deletes an attribute from this object * Requires HTTP path ``key`` * Optional parameters are ``subkey``, ``value``, and ``number`` Examples: .. code:: bash $ ${post} -d 'name=deleteserver1' ${server_url}/entity/basicserver [ "/basicserver/deleteserver1" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${put} -d 'value=joe' ${server_url}/attribute/deleteserver1/group/owner [ { "datatype": "string", "key": "group", "number": null, "subkey": "owner", "value": "joe" } ] HTTP: 200 Content-type: application/json .. code:: bash $ ${delete} ${server_url}/attribute/deleteserver1/group/owner [] HTTP: 200 Content-type: application/json Will create a ``basicserver`` object called ``deleteserver1``, then it will add an attribute (the only attribute so far), then it will delete it. .. code:: bash $ ${post} -d 'name=deleteserver2' ${server_url}/entity/basicserver [ "/basicserver/deleteserver2" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${put} -d 'value=engineering' ${server_url}/attribute/deleteserver2/group [ { "datatype": "string", "key": "group", "number": null, "subkey": null, "value": "engineering" } ] HTTP: 200 Content-type: application/json .. code:: bash $ ${put} -d 'value=joe' ${server_url}/attribute/deleteserver2/group/owner [ { "datatype": "string", "key": "group", "number": null, "subkey": null, "value": "engineering" }, { "datatype": "string", "key": "group", "number": null, "subkey": "owner", "value": "joe" } ] HTTP: 200 Content-type: application/json .. code:: bash $ ${delete} ${server_url}/attribute/deleteserver2/group/owner [ { "datatype": "string", "key": "group", "number": null, "subkey": null, "value": "engineering" } ] HTTP: 200 Content-type: application/json This example should add two attributes with the same key, but different subkey, then it will delete only the second value. """ kwargs = dict(request.params.items()) driver = kwargs.get('driver', None) obj, status, msg = util.get(name, driver) if not obj: return util.dumps(msg, status) qkwargs = {'key': key} if subkey: qkwargs['subkey'] = subkey if number: qkwargs['number'] = number obj.del_attrs(**qkwargs) return util.dumps([util.unclusto(_) for _ in obj.attrs()])
def deallocate(driver, manager): """ Resource managers should allow you to deallocate *things* just the same as allocating *things*. Examples: .. code:: bash $ ${post} -d 'name=ipman2' -d 'gateway=192.168.1.1' -d 'netmask=255.255.255.0' -d 'baseip=192.168.1.10' ${server_url}/resourcemanager/ipmanager { "attrs": [ { "datatype": "string", "key": "baseip", "number": null, "subkey": "property", "value": "192.168.1.10" }, { "datatype": "string", "key": "gateway", "number": null, "subkey": "property", "value": "192.168.1.1" }, { "datatype": "string", "key": "netmask", "number": null, "subkey": "property", "value": "255.255.255.0" } ], "contents": [], "count": 0, "driver": "ipmanager", "name": "ipman2", "parents": [], "type": "resourcemanager" } HTTP: 201 Content-type: application/json $ ${post} -d 'name=names2' -d 'basename=a' ${server_url}/resourcemanager/simpleentitynamemanager { "attrs": [ ... ], "contents": [], "count": 0, "driver": "simpleentitynamemanager", "name": "names2", "parents": [], "type": "resourcemanager" } HTTP: 201 Content-type: application/json $ ${post} -d 'driver=basicserver' ${server_url}/resourcemanager/simpleentitynamemanager/names2 "/basicserver/a01" HTTP: 201 Content-type: application/json $ ${post} -d 'object=a01' ${server_url}/resourcemanager/ipmanager/ipman2 { "datatype": "int", "key": "ip", "number": 0, "subkey": null, "value": 1084752130 } HTTP: 201 Content-type: application/json $ ${delete} -d 'object=a01' ${server_url}/resourcemanager/ipmanager/ipman2 HTTP: 204 Content-type: """ resman, status, msg = _get_resource_manager(manager, driver) if not resman: return util.dumps(msg, status) else: obj = request.params.get('object') if not obj: return util.dumps( 'Cannot deallocate empty, send an object to deallocate', 404 ) obj, status, msg = util.get(obj) resource = request.params.get('resource', ()) # Attempt to deallocate resman.deallocate(obj, resource=resource) return util.dumps(util.unclusto(resman), 204)
def set_attr(name, key, subkey=None, number=None): """ Sets an attribute from this object. If the attribute doesn't exist it will be added, if the attribute already exists then it will be updated. * Requires HTTP parameters ``key`` and ``value`` * Optional parameters are ``subkey`` and ``number`` Example: .. code:: bash $ ${post} -d 'name=setattrserver' ${server_url}/entity/basicserver [ "/basicserver/setattrserver" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${post} -d 'key=group' -d 'value=web' ${server_url}/attribute/setattrserver [ { "datatype": "string", "key": "group", "number": null, "subkey": null, "value": "web" } ] HTTP: 201 Content-type: application/json .. code:: bash $ ${put} -d 'value=db' ${server_url}/attribute/setattrserver/group [ { "datatype": "string", "key": "group", "number": null, "subkey": null, "value": "db" } ] HTTP: 200 Content-type: application/json Will: #. Create the entity ``setattrserver`` #. Add the attribute with ``key=group`` and ``value=web`` #. Update the attribute to ``value=db`` Example: .. code:: bash $ ${post} -d 'name=setattrserver2' ${server_url}/entity/basicserver [ "/basicserver/setattrserver2" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${put} -d 'value=joe' ${server_url}/attribute/setattrserver2/group/owner [ { "datatype": "string", "key": "group", "number": null, "subkey": "owner", "value": "joe" } ] HTTP: 200 Content-type: application/json .. code:: bash $ ${put} -d 'value=bob' ${server_url}/attribute/setattrserver2/group/owner [ { "datatype": "string", "key": "group", "number": null, "subkey": "owner", "value": "bob" } ] HTTP: 200 Content-type: application/json Will: #. Create a new object ``setattrserver2`` of type ``basicserver`` #. Set the attribute with key ``group`` *and* subkey ``owner`` with value ``joe`` to the object ``setattrserver1``. Since this is the only attribute so far, this operation works just like ``add_attr()`` #. Update the attribute we set above, now the ``value`` will read ``bob`` """ kwargs = dict(request.params.items()) obj, status, msg = util.get(name) if not obj: return util.dumps(msg, status) if 'value' not in kwargs.keys(): bottle.abort(412, 'Provide at least "key" and "value"') obj.set_attr(key=key, subkey=subkey, number=number, value=kwargs['value']) return util.dumps([util.unclusto(_) for _ in obj.attrs()])
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)
def show(driver, manager): """ Shows the details of the given resource manager, if it is a resource manager Examples: .. code:: bash $ ${post} -d 'name=nameman2' ${server_url}/resourcemanager/simplenamemanager { "attrs": [ ... ], "contents": [], "count": 0, "driver": "simplenamemanager", "name": "nameman2", "parents": [], "type": "resourcemanager" } HTTP: 201 Content-type: application/json $ ${get} ${server_url}/resourcemanager/simplenamemanager/nameman1 { "attrs": [ ... ], "contents": [], "count": 0, "driver": "simplenamemanager", "name": "nameman1", "parents": [], "type": "resourcemanager" } HTTP: 200 Content-type: application/json Will create the ``nameman2`` resource manager, then show its details. In this case both operations yield the same data. .. code:: bash $ ${get} ${server_url}/resourcemanager/simpleentitynamemanager/nonames "Object \"nonames\" not found (nonames does not exist.)" HTTP: 404 Content-type: application/json Will return a ``404`` error since the resource manager wasn't found .. code:: bash $ ${get} ${server_url}/resourcemanager/nomanager/testnames "The driver \"nomanager\" is not a valid driver" HTTP: 412 Content-type: application/json Will return a ``412`` because the driver ``nomanager`` doesn't exist .. code:: bash $ ${get} ${server_url}/resourcemanager/basicserver/testserver1 "The object \"testserver1\" is not a resource manager" HTTP: 409 Content-type: application/json Will return a ``412`` instead because even though the driver ``basicserver`` exists, it is not a resource manager driver """ obj, status, msg = _get_resource_manager(manager, driver) if not obj: return util.dumps(msg, status) else: return util.dumps(util.show(obj))
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 = {} 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) for ent in ents: result.append(util.unclusto(ent)) result = [] kwargs = {} 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]] ents = clusto.get_entities(**kwargs) for ent in ents: result.append(util.unclusto(ent)) return util.dumps(result)
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)
def _write_attrs(method, name, **kwargs): """ Helper method for reduced code between POST and PUT. Returns a response for the methods calling it. """ if method == 'set': code = 200 if method == 'add': code = 201 else: util.dumps( '"%s" is neither set nor add. How did you get here?' % method, 400) request_kwargs = dict(request.params.items()) driver = kwargs.get('driver', None) obj, status, msg = util.get(name, driver) if not obj: return util.dumps(msg, status) try: # Merge URL values and kwarg values, but do not allow conflicts. for k, v in request_kwargs.items(): if kwargs.get(k) is not None and kwargs[k] != v: raise ValueError( 'Two different values were submitted for "%s": %s' % (k, [kwargs[k], v])) kwargs[k] = v # Additionally capture a value error if the json is bad. json_kwargs = request.json except ValueError as ve: return util.dumps('%s' % (ve, ), 400) if json_kwargs: if request.query: return util.dumps( 'Error: json and query params may not be passed in the same request.', 400) kwargs = json_kwargs # Adds support for bulk attr posting. attrs = [kwargs] if isinstance(kwargs, dict) else kwargs # Check for malformed data or missing pieces before adding any attrs. for attr in attrs: for k in ('key', 'value'): if k not in attr.keys(): bottle.abort(412, 'Provide at least "key" and "value"') if 'number' in attr: try: attr['number'] = int(attr['number']) except ValueError as ve: return util.dumps('%s' % (ve, ), 400) if 'datatype' in attr: datatype = attr.pop('datatype') mask = attr.pop('mask', '%Y-%m-%dT%H:%M:%S.%f') attr['value'] = util.typecast(attr['value'], datatype, mask=mask) for attr in attrs: getattr(obj, method + '_attr')(**attr) return util.dumps([util.unclusto(_) for _ in obj.attrs()], code)
def insert(driver, name): """ Inserts the given device from the request parameters into the object Example: .. code:: bash $ ${post} -d 'name=insertpool' ${server_url}/entity/pool [ "/pool/insertpool" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${post} -d 'name=insertserver' ${server_url}/entity/basicserver [ "/basicserver/insertserver" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${put} -d 'device=insertserver' ${server_url}/entity/pool/insertpool { "attrs": [], "contents": [ "/basicserver/insertserver" ], "driver": "pool", "name": "insertpool", "parents": [] } HTTP: 200 Content-type: application/json Will: #. Create a pool entity called ``insertpool`` #. Create a basicserver entity called ``insertserver`` #. Insert the entity ``insertserver`` into the entity ``insertpool`` Examples: .. code:: bash $ ${post} -d 'name=insertpool2' ${server_url}/entity/pool [ "/pool/insertpool2" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${post} -d 'name=insertserver2' -d 'name=insertserver3' ${server_url}/entity/basicserver [ "/basicserver/insertserver2", "/basicserver/insertserver3" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${put} -d 'device=insertserver2' -d 'device=insertserver3' ${server_url}/entity/pool/insertpool2 { "attrs": [], "contents": [ "/basicserver/insertserver2", "/basicserver/insertserver3" ], "driver": "pool", "name": "insertpool2", "parents": [] } HTTP: 200 Content-type: application/json The above will: #. Create a pool entity called ``insertpool2`` #. Create twp basicserver entities called ``insertserver2`` and ``insertserver3`` #. Insert both basicserver entities into the pool entity """ obj, status, msg = util.get(name, driver) if not obj: return util.dumps(msg, status) devices = request.params.getall('device') 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 inserted into "%s"' % (','.join(notfound), name,)) for devobj in devobjs: if devobj not in obj: obj.insert(devobj) return show(driver, name)
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)
def create(driver): """ This differs from the standard way of creating entities is that resource managers can have a number of extra parameters added to them that not necessarily match any of the other entities. These parameters are defined by each resource manager driver and are pretty much arbitrary. Seems like a good idea to separate these crucial differences. Examples: .. code:: bash $ ${post} -d 'name=nameman1' ${server_url}/resourcemanager/simplenamemanager { "attrs": [ ... ], "contents": [], "count": 0, "driver": "simplenamemanager", "name": "nameman1", "parents": [], "type": "resourcemanager" } HTTP: 201 Content-type: application/json Will create a ``SimpleNameManager`` resource manager named ``namemgr1`` with all default values set. .. code:: bash $ ${post} -d 'name=ipman1' -d 'gateway=192.168.1.1' -d 'netmask=255.255.255.0' -d 'baseip=192.168.1.10' ${server_url}/resourcemanager/ipmanager { "attrs": [ { "datatype": "string", "key": "baseip", "number": null, "subkey": "property", "value": "192.168.1.10" }, { "datatype": "string", "key": "gateway", "number": null, "subkey": "property", "value": "192.168.1.1" }, { "datatype": "string", "key": "netmask", "number": null, "subkey": "property", "value": "255.255.255.0" } ], "contents": [], "count": 0, "driver": "ipmanager", "name": "ipman1", "parents": [], "type": "resourcemanager" } HTTP: 201 Content-type: application/json Will create a ``IPManager`` resource manager named ``ipman1`` with some additional arguments such as ``netmask``, ``gateway`` and ``baseip`` """ if driver not in clusto.driverlist: return util.dumps('Requested driver "%s" does not exist' % (driver,), 412) cls = clusto.driverlist[driver] name = request.params.get('name') request.params.pop('name') # Pass any additional parameters as is to the constructor kwargs = {} for param, value in request.params.items(): kwargs[param] = value found = None try: found = util.unclusto(clusto.get_by_name(name)) except LookupError: pass obj = clusto.get_or_create(name, cls, **kwargs) headers = {} if found: headers['Warnings'] = 'Resource manager "%s" already exists' % (found,) code = 201 if found: code = 202 return util.dumps(util.show(obj), code, headers=headers)
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)
def allocate(driver, manager): """ This allocates a new *resource* to a given *thing*. Said thing can be either a *driver* (and the result will be a newly created object subclasses from this driver) or an *object*, and the resource manager will allocate (bind) a resource to it. Examples: .. code:: bash $ ${post} -d 'name=allocator' ${server_url}/resourcemanager/simpleentitynamemanager { "attrs": [ ... ], "contents": [], "count": 0, "driver": "simpleentitynamemanager", "name": "allocator", "parents": [], "type": "resourcemanager" } HTTP: 201 Content-type: application/json $ ${post} -d 'driver=basicserver' ${server_url}/resourcemanager/simpleentitynamemanager/allocator "/basicserver/01" HTTP: 201 Content-type: application/json Will request a new name from the object ``allocator`` (which is an object of ``SimpleEntityManager`` that we just created with default values) and then it will create a new ``BasicServer`` object. .. code:: bash $ ${post} -d 'driver=basicserver' -d 'resource=99' ${server_url}/resourcemanager/simpleentitynamemanager/allocator "/basicserver/99" HTTP: 201 Content-type: application/json Will create a new ``BasicServer`` object from the ``testnames`` resource manager with the specific name of ``s99``. """ obj, status, msg = _get_resource_manager(manager, driver) if not obj: return util.dumps(msg, status) else: d = request.params.get('driver') o = request.params.get('object') thing = d or o if not thing: return util.dumps( 'Cannot allocate an empty thing, send one of ' '"driver", "object"', 404 ) if d: thing = clusto.driverlist.get(thing) else: thing = clusto.get_by_name(thing) if not thing: return util.dumps('Thing was "%s" not found' % (d or o,), 404) resource = request.params.get('resource', default=()) r = obj.allocate(thing, resource) # The returned value can be anything such a string, number, or attribute return util.dumps(util.unclusto(r), 201)
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)
def list(driver=None): """ Lists all resource managers found in the clusto database. Optionally you can list all resource managers that match the given ``driver`` Examples: .. code:: bash $ ${post} -d 'name=zmanager' ${server_url}/resourcemanager/simplenamemanager { "attrs": [ ... ], "contents": [], "count": 0, "driver": "simplenamemanager", "name": "zmanager", "parents": [], "type": "resourcemanager" } HTTP: 201 Content-type: application/json The above will create a simple name manager called "zmanager" .. code:: bash $ ${get} ${server_url}/resourcemanager/ [ "/simpleentitynamemanager/testnames", "/simplenamemanager/zmanager" ] HTTP: 200 Content-type: application/json The above will list all resource managers in clusto, which should have "zmanager" .. code:: bash $ ${get} ${server_url}/resourcemanager/simpleentitynamemanager [ "/simpleentitynamemanager/testnames" ] HTTP: 200 Content-type: application/json Will list all resource managers of driver ``SimpleNameManager`` .. code:: bash $ ${get} ${server_url}/resourcemanager/notadriver "Not a valid driver \"notadriver\" (driver name notadriver doesn't exist.)" HTTP: 404 Content-type: application/json Will return a ``404`` error because that resource manager driver doesn't exist """ result = [] if driver: try: ents = clusto.get_entities(clusto_drivers=[driver]) except NameError as ne: return util.dumps( 'Not a valid driver "%s" (%s)' % (driver, ne,), 404 ) else: # Until we fix the ipmanager snafu, gotta check for both types ents = clusto.get_entities(clusto_types=['resourcemanager']) for ent in ents: # Kind of shitty way, but have to make sure these are all resource managers if issubclass(ent.__class__, drivers.resourcemanagers.ResourceManager): result.append(util.unclusto(ent)) return util.dumps(result)
def add_attr(name): """ Add an attribute to this object. * Requires HTTP parameters ``name``, ``key``, and ``value`` * Optional parameters are ``subkey`` and ``number`` Example: .. code:: bash $ ${post} -d 'name=addattrserver' ${server_url}/entity/basicserver [ "/basicserver/addattrserver" ] HTTP: 201 Content-type: application/json .. code:: bash $ ${post} -d 'key=group' -d 'value=web' ${server_url}/attribute/addattrserver [ { "datatype": "string", "key": "group", "number": null, "subkey": null, "value": "web" } ] HTTP: 201 Content-type: application/json Will: #. Create an entity called ``addattrserver`` #. Add the attribute with ``key=group`` and ``value=web`` to it Example: .. code:: bash $ ${post} -d 'key=group' -d 'subkey=owner' -d 'value=web' ${server_url}/attribute/addattrserver [ { "datatype": "string", "key": "group", "number": null, "subkey": null, "value": "web" }, { "datatype": "string", "key": "group", "number": null, "subkey": "owner", "value": "web" } ] HTTP: 201 Content-type: application/json Will add the attribute with key ``group`` *and* subkey ``owner`` *and* value ``joe`` to the previously created entity ``addattrserver`` """ kwargs = dict(request.params.items()) driver = kwargs.get('driver', None) obj, status, msg = util.get(name, driver) if not obj: return util.dumps(msg, status) for k in ('key', 'value'): if k not in kwargs.keys(): bottle.abort(412, 'Provide at least "key" and "value"') if 'number' in kwargs: kwargs['number'] = int(kwargs['number']) obj.add_attr(**kwargs) return util.dumps([util.unclusto(_) for _ in obj.attrs()], 201)