def tombstone(uid): """ Handle all tombstone operations. The only allowed methods are POST and DELETE; any other verb will return a 405. """ try: rsrc_api.get(uid) except exc.TombstoneError as e: if request.method == 'DELETE': if e.uid == uid: rsrc_api.delete(uid, False) return '', 204 else: return _tombstone_response(e, uid) elif request.method == 'POST': if e.uid == uid: rsrc_uri = rsrc_api.resurrect(uid) headers = {'Location': rsrc_uri} return rsrc_uri, 201, headers else: return _tombstone_response(e, uid) else: return 'Method Not Allowed.', 405 except exc.ResourceNotExistsError as e: return str(e), 404 else: return '', 404
def delete_resource(uid): ''' Delete a resource and optionally leave a tombstone. This behaves differently from FCREPO. A tombstone indicated that the resource is no longer available at its current location, but its historic snapshots still are. Also, deleting a resource with a tombstone creates one more version snapshot of the resource prior to being deleted. In order to completely wipe out all traces of a resource, the tombstone must be deleted as well, or the `Prefer:no-tombstone` header can be used. The latter will forget (completely delete) the resource immediately. ''' headers = std_headers if 'prefer' in request.headers: prefer = g.tbox.parse_rfc7240(request.headers['prefer']) leave_tstone = 'no-tombstone' not in prefer else: leave_tstone = True try: rsrc_api.delete(uid, leave_tstone) except ResourceNotExistsError as e: return str(e), 404 except TombstoneError as e: return _tombstone_response(e, uid) return '', 204, headers
def test_hard_delete_descendants(self): """ Forget a resource with all its descendants. """ uid = '/test_hard_delete_descendants01' rsrc_api.create_or_replace(uid) for i in range(1, 4): rsrc_api.create_or_replace('{}/child{}'.format(uid, i)) for j in range(i): rsrc_api.create_or_replace('{}/child{}/grandchild{}'.format( uid, i, j)) rsrc_api.delete(uid, False) with pytest.raises(ResourceNotExistsError): rsrc_api.get(uid) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect(uid) for i in range(1, 4): with pytest.raises(ResourceNotExistsError): rsrc_api.get('{}/child{}'.format(uid, i)) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect('{}/child{}'.format(uid, i)) for j in range(i): with pytest.raises(ResourceNotExistsError): rsrc_api.get('{}/child{}/grandchild{}'.format( uid, i, j)) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect('{}/child{}/grandchild{}'.format( uid, i, j))
def test_soft_delete(self): """ Soft-delete (bury) a resource. """ uid = '/test_soft_delete01' rsrc_api.create_or_replace(uid) rsrc_api.delete(uid) with pytest.raises(TombstoneError): rsrc_api.get(uid)
def test_hard_delete(self): """ Hard-delete (forget) a resource. """ uid = '/test_hard_delete01' rsrc_api.create_or_replace(uid) rsrc_api.delete(uid, False) with pytest.raises(ResourceNotExistsError): rsrc_api.get(uid) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect(uid)
def test_resurrect(self): """ Restore (resurrect) a soft-deleted resource. """ uid = '/test_soft_delete02' rsrc_api.create_or_replace(uid) rsrc_api.delete(uid) rsrc_api.resurrect(uid) rsrc = rsrc_api.get(uid) with env.app_globals.rdf_store.txn_ctx(): assert nsc['ldp'].Resource in rsrc.ldp_types
def test_delete_children(self): """ Soft-delete a resource with children. """ uid = '/test_soft_delete_children01' rsrc_api.create_or_replace(uid) for i in range(3): rsrc_api.create_or_replace('{}/child{}'.format(uid, i)) rsrc_api.delete(uid) with pytest.raises(TombstoneError): rsrc_api.get(uid) for i in range(3): with pytest.raises(TombstoneError): rsrc_api.get('{}/child{}'.format(uid, i)) # Cannot resurrect children of a tombstone. with pytest.raises(TombstoneError): rsrc_api.resurrect('{}/child{}'.format(uid, i))
def test_hard_delete_children(self): """ Hard-delete (forget) a resource with its children. This uses fixtures from the previous test. """ uid = '/test_hard_delete_children01' rsrc_api.create_or_replace(uid) for i in range(3): rsrc_api.create_or_replace('{}/child{}'.format(uid, i)) rsrc_api.delete(uid, False) with pytest.raises(ResourceNotExistsError): rsrc_api.get(uid) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect(uid) for i in range(3): with pytest.raises(ResourceNotExistsError): rsrc_api.get('{}/child{}'.format(uid, i)) with pytest.raises(ResourceNotExistsError): rsrc_api.resurrect('{}/child{}'.format(uid, i))
def delete_resource(uid): """ Delete a resource and optionally leave a tombstone. This behaves differently from FCREPO. A tombstone indicated that the resource is no longer available at its current location, but its historic snapshots still are. Also, deleting a resource with a tombstone creates one more version snapshot of the resource prior to being deleted. In order to completely wipe out all traces of a resource, the tombstone must be deleted as well, or the ``Prefer:no-tombstone`` header can be used. The latter will forget (completely delete) the resource immediately. """ # Fist check if it's not a 404 or a 410. try: if not rsrc_api.exists(uid): return '', 404 except exc.TombstoneError as e: return _tombstone_response(e, uid) # Then process the condition headers. cond_ret = _process_cond_headers(uid, request.headers, False) if cond_ret: return cond_ret headers = std_headers.copy() if 'prefer' in request.headers: prefer = toolbox.parse_rfc7240(request.headers['prefer']) leave_tstone = 'no-tombstone' not in prefer else: leave_tstone = True rsrc_api.delete(uid, leave_tstone) return '', 204, headers
def run(mode, endpoint, count, parent, method, delete_container, graph_size, image_size, resource_type, plot): """ Run the benchmark. """ method = method.lower() if method not in ('post', 'put'): raise ValueError(f'Insertion method not supported: {method}') mode = mode.lower() if mode == 'ldp': parent = '{}/{}'.format(endpoint.strip('/'), parent.strip('/')) if delete_container: print('Removing previously existing container.') requests.delete(parent) requests.delete(f'{parent}/fcr:tombstone') requests.put(parent) elif mode == 'python': from lakesuperior import env_setup from lakesuperior.api import resource as rsrc_api if delete_container: try: print('Removing previously existing container.') rsrc_api.delete(parent, soft=False) except ResourceNotExistsError: pass rsrc_api.create_or_replace(parent) else: raise ValueError(f'Mode not supported: {mode}') if resource_type != 'r': # Set image parameters. ims = max(image_size - image_size % 8, 128) tn = ims // 32 # URI used to establish an in-repo relationship. This is set to # the most recently created resource in each loop. ref = parent print(f'Inserting {count} children under {parent}.') wclock_start = arrow.utcnow() if plot: print('Results will be plotted.') # Plot coordinates: X is request count, Y is request timing. px = [] py = [] plt.xlabel('Requests') plt.ylabel('ms per request') plt.title('Lakesuperior / FCREPO Benchmark') try: for i in range(1, count + 1): if mode == 'ldp': dest = (f'{parent}/{uuid4()}' if method == 'put' else parent) else: dest = (path.join(parent, str(uuid4())) if method == 'put' else parent) if resource_type == 'r' or (resource_type == 'b' and i % 2 == 0): data = random_graph(graph_size, ref) headers = {'content-type': 'text/turtle'} else: img = random_image(tn=tn, ims=ims) data = img['content'] data.seek(0) headers = { 'content-type': 'image/png', 'content-disposition': 'attachment; filename="{}"'.format(uuid4()) } # Start timing after generating the data. ckpt = arrow.utcnow() if i == 1: tcounter = ckpt - ckpt prev_tcounter = tcounter #import pdb; pdb.set_trace() ref = (_ingest_ldp(method, dest, data, headers, ref) if mode == 'ldp' else _ingest_py(method, dest, data, ref)) tcounter += (arrow.utcnow() - ckpt) if i % 10 == 0: avg10 = (tcounter - prev_tcounter) / 10 print(f'Record: {i}\tTime elapsed: {tcounter}\t' f'Per resource: {avg10}') prev_tcounter = tcounter if plot: px.append(i) # Divide by 1000 for µs → ms py.append(avg10.microseconds // 1000) except KeyboardInterrupt: print('Interrupted after {} iterations.'.format(i)) wclock = arrow.utcnow() - wclock_start print(f'Total elapsed time: {wclock}') print(f'Total time spent ingesting resources: {tcounter}') print(f'Average time per resource: {tcounter.total_seconds()/i}') if plot: if resource_type == 'r': type_label = 'LDP-RS' elif resource_type == 'n': type_label = 'LDP-NR' else: type_label = 'LDP-RS + LDP-NR' label = (f'{parent}; {method.upper()}; {graph_size} trp/graph; ' f'{type_label}') plt.plot(px, py, label=label) plt.legend() plt.show()