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 _get_resource_manager(manager, driver): "A wrapper because an extra check has to be made :(" obj, status, msg = util.get(manager, driver) if obj: if not issubclass(obj.__class__, drivers.resourcemanagers.ResourceManager): msg = 'The object "%s" is not a resource manager' % (manager,) status = 409 obj = None else: pass else: pass return obj, status, msg
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 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 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 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 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 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 _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 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 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)