async def GET_Chunk(request): log.request(request) app = request.app params = request.rel_url.query chunk_id = request.match_info.get('id') if not chunk_id: msg = "Missing chunk id" log.error(msg) raise HTTPBadRequest(reason=msg) if not isValidUuid(chunk_id, "Chunk"): msg = "Invalid chunk id: {}".format(chunk_id) log.warn(msg) raise HTTPBadRequest(reason=msg) validateInPartition(app, chunk_id) log.debug("request params: {}".format(list(params.keys()))) if "dset" not in params: msg = "Missing dset in GET request" log.error(msg) raise HTTPBadRequest(reason=msg) dset_json = json.loads(params["dset"]) log.debug("dset_json: {}".format(dset_json)) type_json = dset_json["type"] dims = getChunkLayout(dset_json) log.debug("got dims: {}".format(dims)) rank = len(dims) # get chunk selection from query params if "select" in params: log.debug("select: {}".format(params["select"])) selection = [] for i in range(rank): dim_slice = getSliceQueryParam(request, i, dims[i]) selection.append(dim_slice) selection = tuple(selection) log.debug("got selection: {}".format(selection)) dt = createDataType(type_json) log.debug("dtype: {}".format(dt)) rank = len(dims) if rank == 0: msg = "No dimension passed to GET chunk request" log.error(msg) raise HTTPBadRequest(reason=msg) if len(selection) != rank: msg = "Selection rank does not match shape rank" log.error(msg) raise HTTPBadRequest(reason=msg) for i in range(rank): s = selection[i] log.debug("selection[{}]: {}".format(i, s)) s3path = None s3offset = 0 s3size = 0 if "s3path" in params: s3path = params["s3path"] log.debug(f"GET_Chunk - useing s3path: {s3path}") if "s3offset" in params: try: s3offset = int(params["s3offset"]) except ValueError: log.error(f"invalid s3offset params: {params['s3offset']}") raise HTTPBadRequest() if "s3size" in params: try: s3size = int(params["s3size"]) except ValueError: log.error(f"invalid s3size params: {params['s3sieze']}") raise HTTPBadRequest() chunk_arr = await getChunk(app, chunk_id, dset_json, s3path=s3path, s3offset=s3offset, s3size=s3size) if chunk_arr is None: # return a 404 msg = "Chunk {} does not exist".format(chunk_id) log.info(msg) raise HTTPNotFound() resp = None if "query" in params: # do query selection query = params["query"] log.info("query: {}".format(query)) if rank != 1: msg = "Query selection only supported for one dimensional arrays" log.warn(msg) raise HTTPBadRequest(reason=msg) limit = 0 if "Limit" in params: limit = int(params["Limit"]) values = [] indices = [] field_names = [] if dt.fields: field_names = list(dt.fields.keys()) x = chunk_arr[selection] log.debug("x: {}".format(x)) eval_str = getEvalStr(query, "x", field_names) log.debug("eval_str: {}".format(eval_str)) where_result = np.where(eval(eval_str)) log.debug("where_result: {}".format(where_result)) where_result_index = where_result[0] log.debug("whare_result index: {}".format(where_result_index)) log.debug("boolean selection: {}".format(x[where_result_index])) s = selection[0] count = 0 for index in where_result_index: log.debug("index: {}".format(index)) value = x[index].tolist() log.debug("value: {}".format(value)) json_val = bytesArrayToList(value) log.debug("json_value: {}".format(json_val)) json_index = index.tolist() * s.step + s.start # adjust for selection indices.append(json_index) values.append(json_val) count += 1 if limit > 0 and count >= limit: log.info("got limit items") break query_result = {} query_result["index"] = indices query_result["value"] = values log.info(f"query_result retiurning: {len(indices)} rows") log.debug(f"query_result: {query_result}") resp = json_response(query_result) else: # get requested data output_arr = chunk_arr[selection] output_data = arrayToBytes(output_arr) # write response try: resp = StreamResponse() resp.headers['Content-Type'] = "application/octet-stream" resp.content_length = len(output_data) await resp.prepare(request) await resp.write(output_data) except Exception as e: log.error(f"Exception during binary data write: {e}") raise HTTPInternalServerError() finally: await resp.write_eof() return resp
async def PUT_Chunk(request): log.request(request) app = request.app params = request.rel_url.query query = None if "query" in params: query = params["query"] log.info(f"PUT_Chunk query: {query}") chunk_id = request.match_info.get('id') if not chunk_id: msg = "Missing chunk id" log.error(msg) raise HTTPBadRequest(reason=msg) if not isValidUuid(chunk_id, "Chunk"): msg = f"Invalid chunk id: {chunk_id}" log.warn(msg) raise HTTPBadRequest(reason=msg) if not request.has_body: msg = "PUT Value with no body" log.warn(msg) raise HTTPBadRequest(reason=msg) if "bucket" in params: bucket = params["bucket"] log.debug(f"PUT_Chunk using bucket: {bucket}") else: bucket = None if query: expected_content_type = "text/plain; charset=utf-8" else: expected_content_type = "application/octet-stream" if "Content-Type" in request.headers: # client should use "application/octet-stream" for binary transfer content_type = request.headers["Content-Type"] if content_type != expected_content_type: msg = f"Unexpected content_type: {content_type}" log.error(msg) raise HTTPBadRequest(reason=msg) validateInPartition(app, chunk_id) if "dset" in params: msg = "Unexpected param dset in GET request" log.error(msg) raise HTTPBadRequest(reason=msg) log.debug(f"PUT_Chunk - id: {chunk_id}") dset_id = getDatasetId(chunk_id) dset_json = await get_metadata_obj(app, dset_id, bucket=bucket) log.debug(f"dset_json: {dset_json}") dims = getChunkLayout(dset_json) if "root" not in dset_json: msg = "expected root key in dset_json" log.error(msg) raise KeyError(msg) rank = len(dims) # get chunk selection from query params selection = [] for i in range(rank): dim_slice = getSliceQueryParam(request, i, dims[i]) selection.append(dim_slice) selection = tuple(selection) log.debug(f"got selection: {selection}") type_json = dset_json["type"] itemsize = 'H5T_VARIABLE' if "size" in type_json: itemsize = type_json["size"] dt = createDataType(type_json) log.debug(f"dtype: {dt}") if rank == 0: msg = "No dimension passed to PUT chunk request" log.error(msg) raise HTTPBadRequest(reason=msg) if len(selection) != rank: msg = "Selection rank does not match shape rank" log.error(msg) raise HTTPBadRequest(reason=msg) for i in range(rank): s = selection[i] log.debug(f"selection[{i}]: {s}") mshape = getSelectionShape(selection) log.debug(f"mshape: {mshape}") num_elements = 1 for extent in mshape: num_elements *= extent resp = {} query_update = None limit = 0 chunk_init = True input_arr = None if query: if not dt.fields: log.error("expected compound dtype for PUT query") raise HTTPInternalServerError() query_update = await request.json() log.debug(f"query_update: {query_update}") if "Limit" in params: limit = int(params["Limit"]) chunk_init = False else: # regular chunk update # check that the content_length is what we expect if itemsize != 'H5T_VARIABLE': log.debug(f"expect content_length: {num_elements*itemsize}") log.debug(f"actual content_length: {request.content_length}") if itemsize != 'H5T_VARIABLE' and (num_elements * itemsize) != request.content_length: msg = f"Expected content_length of: {num_elements*itemsize}, but got: {request.content_length}" log.error(msg) raise HTTPBadRequest(reason=msg) # create a numpy array for incoming data input_bytes = await request_read( request ) # TBD - will it cause problems when failures are raised before reading data? if len(input_bytes) != request.content_length: msg = f"Read {len(input_bytes)} bytes, expecting: {request.content_length}" log.error(msg) raise HTTPInternalServerError() input_arr = bytesToArray(input_bytes, dt, mshape) # TBD: Skip read if the input shape is the entire chunk? chunk_arr = await getChunk(app, chunk_id, dset_json, chunk_init=chunk_init, bucket=bucket) is_dirty = False if query: values = [] indices = [] if chunk_arr is not None: # do query selection limit = 0 if "Limit" in params: limit = int(params["Limit"]) field_names = list(dt.fields.keys()) replace_mask = [ None, ] * len(field_names) for i in range(len(field_names)): field_name = field_names[i] if field_name in query_update: replace_mask[i] = query_update[field_name] log.debug(f"replace_mask: {replace_mask}") x = chunk_arr[selection] log.debug(f"put_query - x: {x}") eval_str = getEvalStr(query, "x", field_names) log.debug(f"put_query - eval_str: {eval_str}") where_result = np.where(eval(eval_str)) log.debug(f"put_query - where_result: {where_result}") where_result_index = where_result[0] log.debug(f"put_query - whare_result index: {where_result_index}") log.debug( f"put_query - boolean selection: {x[where_result_index]}") s = selection[0] count = 0 for index in where_result_index: log.debug(f"put_query - index: {index}") value = x[index] log.debug(f"put_query - original value: {value}") for i in range(len(field_names)): if replace_mask[i] is not None: value[i] = replace_mask[i] log.debug(f"put_query - modified value: {value}") x[index] = value json_val = bytesArrayToList(value) log.debug(f"put_query - json_value: {json_val}") json_index = index.tolist( ) * s.step + s.start # adjust for selection indices.append(json_index) values.append(json_val) count += 1 is_dirty = True if limit > 0 and count >= limit: log.info("put_query - got limit items") break query_result = {} query_result["index"] = indices query_result["value"] = values log.info(f"query_result retiurning: {len(indices)} rows") log.debug(f"query_result: {query_result}") resp = json_response(query_result) else: # update chunk array chunk_arr[selection] = input_arr is_dirty = True resp = json_response({}, status=201) if is_dirty: chunk_cache = app["chunk_cache"] chunk_cache.setDirty(chunk_id) log.info(f"PUT_Chunk dirty cache count: {chunk_cache.dirtyCount}") # async write to S3 dirty_ids = app["dirty_ids"] now = int(time.time()) dirty_ids[chunk_id] = (now, bucket) # chunk update successful log.response(request, resp=resp) return resp