Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
978e672
add h5json package
jreadey Apr 14, 2025
abb5d0c
temp use of github branch for h5json ref
jreadey Apr 14, 2025
ed44afa
remove array_util test
jreadey Apr 14, 2025
bdff6e4
use h5json for ndarray_compare function
jreadey Apr 14, 2025
3904cf9
use h5json objid funcs
jreadey Apr 23, 2025
e1926c0
add nodeUtil.py
jreadey Apr 23, 2025
ae4579f
fix parameter for createObjId call
jreadey Apr 23, 2025
d6cad74
fix collection name for use with h5json
jreadey Apr 23, 2025
6add48a
use connsistent collection name for isValidUuid
jreadey Apr 23, 2025
b13321c
fix flake8 format errors
jreadey Apr 23, 2025
fee9390
fix flake8 error in testall
jreadey Apr 23, 2025
f1b1cab
use h5json for unit test id
jreadey Apr 23, 2025
5dc3f76
restrict version on numcodecs
jreadey Apr 24, 2025
fb17e10
allow client to generate obj ids
jreadey Apr 30, 2025
3be18a0
enable attributes to be included with POST req
jreadey May 7, 2025
00d7c96
add create timestamps for attributes in obj create
jreadey May 7, 2025
47b9a6e
enable links to be initialized in post groups
jreadey May 7, 2025
d9c3e87
support dataset value init in post request
jreadey May 8, 2025
4ab24fc
add compound init value test
jreadey May 9, 2025
fc3ad68
added post data with compound data initializer
jreadey May 9, 2025
8a18945
add post_crawler class
jreadey May 15, 2025
a8ec66d
avoid exception for mkdir race condition
jreadey May 15, 2025
41e23e9
use domain crawler to create links for post group multi
jreadey May 15, 2025
7cfa3d6
added multi create for datatype objs
jreadey May 16, 2025
ef746d0
added datatype test with no type in body
jreadey May 18, 2025
b1af9bc
modularize dataset creation args processing
jreadey May 20, 2025
52f42f3
refacotr post dataset args to service_lib.py
jreadey May 21, 2025
ce45804
add multi-dataset test with init data
jreadey May 21, 2025
88e0691
allow client group id for PUT domain
jreadey Jun 6, 2025
7561534
fix np.frombuffer error
jreadey Jun 8, 2025
25c4cf3
fix dsetUtil flake errors
jreadey Jun 8, 2025
5cc77e7
expanded link test
jreadey Jul 14, 2025
45f3aa5
added config to test high latency storage
jreadey Jul 16, 2025
ff1c043
added put_data action for DomainCrawler
jreadey Jul 22, 2025
cda56cf
fix for hang in DomainCrawler put_data handler
jreadey Jul 23, 2025
5a2d4d6
reduce log verbosity
jreadey Jul 23, 2025
053395c
fix for regression with h5pyd master branch
jreadey Jul 29, 2025
78127f1
enable client-based timestamps for attribute and link creation
jreadey Sep 8, 2025
f96b34c
remove python 3.9 from .git workflow
jreadey Sep 9, 2025
03e413f
adjust min time for time skew test
jreadey Sep 9, 2025
b6016e0
use hdf5-json util classes
jreadey Oct 29, 2025
61d38fd
update requirement.txt
jreadey Nov 13, 2025
73d8223
updates to support h5json latest
joshStillerman Dec 16, 2025
a2ca1ee
updated for new hdf5-json methods
jreadey Dec 26, 2025
55c8598
update for h5json changes
jreadey Jan 4, 2026
77042d8
added consolidated metadata support
jreadey Jan 6, 2026
23bb24b
fix for use of H5S_UNLIMITED in maxdims
jreadey Jan 6, 2026
c66d632
fix for domain_test
jreadey Jan 6, 2026
6917c5d
refactor linkUtil with h5json
jreadey Jan 8, 2026
99fda2d
fix for attr uninit values
jreadey Feb 3, 2026
2bafb51
check for zero-length domain name
jreadey Feb 11, 2026
b4f4658
tmp ref to github for h5json
jreadey Mar 25, 2026
179ee10
fixed ref to timeUtil
jreadey Mar 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions hsds/group_dn.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,19 @@ async def POST_Group(request):
else:
attrs = {}

if "links" in body:
# initialize links
links = body["links"]
log.debug(f"POST Group with links: {links}")
else:
links = {}

group_json = {
"id": group_id,
"root": root_id,
"created": now,
"lastModified": now,
"links": {},
"links": links,
"attributes": attrs,
}

Expand All @@ -153,7 +160,7 @@ async def POST_Group(request):
resp_json["root"] = root_id
resp_json["created"] = group_json["created"]
resp_json["lastModified"] = group_json["lastModified"]
resp_json["linkCount"] = 0
resp_json["linkCount"] = len(links)
resp_json["attributeCount"] = len(attrs)

resp = json_response(resp_json, status=201)
Expand Down
72 changes: 57 additions & 15 deletions hsds/group_sn.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# group handler for service node of hsds cluster
#

import time

from aiohttp.web_exceptions import HTTPBadRequest, HTTPForbidden, HTTPNotFound
from json import JSONDecodeError

Expand All @@ -23,11 +25,12 @@
from .util.authUtil import validateUserPassword
from .util.domainUtil import getDomainFromRequest, isValidDomain
from .util.domainUtil import getBucketForDomain, getPathForDomain, verifyRoot
from .util.linkUtil import validateLinkName
from .util.linkUtil import validateLinkName, getLinkClass
from .servicenode_lib import getDomainJson, getObjectJson, validateAction
from .servicenode_lib import getObjectIdByPath, getPathForObjectId
from .servicenode_lib import createObject, createObjectByPath, deleteObject
from . import hsds_logger as log
from . import config


async def GET_Group(request):
Expand Down Expand Up @@ -189,6 +192,7 @@ async def POST_Group(request):
h5path = None
creation_props = None
attrs = None
links = None

if request.has_body:
try:
Expand Down Expand Up @@ -236,28 +240,66 @@ async def POST_Group(request):
creation_props = body["creationProperties"]
if "attributes" in body:
attrs = body["attributes"]
if not isinstance(attrs, dict):
msg = f"POST_Groups expected dict for for links, but got: {type(links)}"
log.warn(msg)
raise HTTPBadRequest(reason=msg)
log.debug(f"POST Group attributes: {attrs}")
if "links" in body:
links = body["links"]
if not isinstance(links, dict):
msg = f"POST_Groups expected dict for for links, but got: {type(links)}"
log.warn(msg)
raise HTTPBadRequest(reason=msg)
# validate the links
now = time.time()

for title in links:
try:
validateLinkName(title)
link_item = links[title]
link_class = getLinkClass(link_item)
if "class" in link_item:
if link_class != link_item["class"]:
msg = f"expected link class of: {link_class} but got {link_item}"
log.warn(msg)
raise HTTPBadRequest(reason=msg)
else:
link_item["class"] = link_class
getLinkClass(link_item)
if "created" in link_item:
created = link_item["created"]
# allow "pre-dated" attributes if recent enough
predate_max_time = config.get("predate_max_time", default=10.0)
if now - created > predate_max_time:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparison seems backwards. If I understand correctly, the difference between current time and creation time should need to be under the max time, not above it

link_item["created"] = created
else:
log.warn("stale created timestamp for link, ignoring")
if "created" not in link_item:
link_item["created"] = now

except ValueError:
raise HTTPBadRequest(reason="invalid link item")

kwargs = {"bucket": bucket}
if obj_id:
kwargs["obj_id"] = obj_id
if creation_props:
kwargs["creation_props"] = creation_props
if attrs:
kwargs["attrs"] = attrs
if links:
kwargs["links"] = links

if parent_id:
kwargs = {"bucket": bucket, "parent_id": parent_id, "h5path": h5path}
if obj_id:
kwargs["obj_id"] = obj_id
if creation_props:
kwargs["creation_props"] = creation_props
if attrs:
kwargs["attrs"] = attrs
kwargs["parent_id"] = parent_id
kwargs["h5path"] = h5path
if implicit:
kwargs["implicit"] = True
group_json = await createObjectByPath(app, **kwargs)
else:
# create an anonymous group
kwargs = {"bucket": bucket, "root_id": root_id}
if obj_id:
kwargs["obj_id"] = obj_id
if creation_props:
kwargs["creation_props"] = creation_props
if attrs:
kwargs["attrs"] = attrs
kwargs["root_id"] = root_id
group_json = await createObject(app, **kwargs)

log.debug(f"returning resp: {group_json}")
Expand Down
6 changes: 5 additions & 1 deletion hsds/link_sn.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# service node of hsds cluster
#

from aiohttp.web_exceptions import HTTPBadRequest
from aiohttp.web_exceptions import HTTPBadRequest, HTTPInternalServerError
from json import JSONDecodeError

from h5json.objid import isValidUuid, getCollectionForId
Expand Down Expand Up @@ -142,6 +142,10 @@ async def GET_Links(request):

# mix in collection key, target and hrefs
for link in links:
if "class" not in link:
log.error("expected to find class key in link")
raise HTTPInternalServerError()

if link["class"] == "H5L_TYPE_HARD":
collection_name = getCollectionForId(link["id"])
link["collection"] = collection_name
Expand Down
19 changes: 18 additions & 1 deletion hsds/servicenode_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,7 @@ async def createObject(app,
layout=None,
creation_props=None,
attrs=None,
links=None,
bucket=None):
""" create a group, ctype, or dataset object and return object json
Determination on whether a group, ctype, or dataset is created is based on:
Expand All @@ -1319,6 +1320,8 @@ async def createObject(app,
log.debug(f" cprops: {creation_props}")
if attrs:
log.debug(f" attrs: {attrs}")
if links:
log.debug(f" links: {links}")

if obj_id:
log.debug(f"using client supplied id: {obj_id}")
Expand Down Expand Up @@ -1347,8 +1350,13 @@ async def createObject(app,
attrs_json = {"attributes": attrs}
attr_items = await getAttributesFromRequest(app, attrs_json, **kwargs)
log.debug(f"got attr_items: {attr_items}")

obj_json["attributes"] = attr_items
if links:
if collection != "groups":
msg = "links can only be used with groups"
log.warn(msg)
raise HTTPBadRequest(reason=msg)
obj_json["links"] = links
log.debug(f"create {collection} obj, body: {obj_json}")
dn_url = getDataNodeUrl(app, obj_id)
req = f"{dn_url}/{collection}"
Expand All @@ -1368,6 +1376,7 @@ async def createObjectByPath(app,
layout=None,
creation_props=None,
attrs=None,
links=None,
bucket=None):

""" create an object at the designated path relative to the parent.
Expand All @@ -1394,6 +1403,12 @@ async def createObjectByPath(app,
log.debug(f" cprops: {creation_props}")
if attrs:
log.debug(f" attrs: {attrs}")
if links:
log.debug(f" links: {links}")
if obj_type:
msg = "only group objects can have links"
log.warn(msg)
raise HTTPBadRequest(reason=msg)

root_id = getRootObjId(parent_id)

Expand Down Expand Up @@ -1474,6 +1489,8 @@ async def createObjectByPath(app,
kwargs["creation_props"] = creation_props
if attrs:
kwargs["attrs"] = attrs
if links:
kwargs["links"] = links
if obj_id:
kwargs["obj_id"] = obj_id
obj_json = await createObject(app, **kwargs)
Expand Down
60 changes: 59 additions & 1 deletion tests/integ/group_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def testPostWithLink(self):
self.assertEqual(rspJson["linkCount"], 0)
self.assertEqual(rspJson["attributeCount"], 0)
new_group_id = rspJson["id"]
self.assertTrue(helper.validateId(rspJson["id"]))
self.assertTrue(helper.validateId(new_group_id))
self.assertTrue(new_group_id != root_uuid)

# get root group and verify link count is 1
Expand Down Expand Up @@ -418,6 +418,64 @@ def testPostWithAttributes(self):
self.assertTrue("attributes") in rspJson
self.assertEqual(len(rspJson["attributes"]), attr_count)

def testPostWithLinks(self):
# test POST with attribute initialization
print("testPostWithLinks", self.base_domain)
headers = helper.getRequestHeaders(domain=self.base_domain)

# get root id
req = helper.getEndpoint() + "/"
rsp = self.session.get(req, headers=headers)
self.assertEqual(rsp.status_code, 200)
rspJson = json.loads(rsp.text)
root_uuid = rspJson["root"]
helper.validateId(root_uuid)

# some objects to link
link_count = 4
links = {}
req = helper.getEndpoint() + "/groups"

for i in range(link_count):
rsp = self.session.post(req, headers=headers)
self.assertEqual(rsp.status_code, 201)
rspJson = json.loads(rsp.text)
group_id = rspJson["id"]
self.assertTrue(helper.validateId(group_id))
links[f"obj_{i}"] = {"id": group_id}

# create new group
payload = {"links": links, "link": {"id": root_uuid, "name": "g1"}}
req = helper.getEndpoint() + "/groups"
rsp = self.session.post(req, data=json.dumps(payload), headers=headers)
self.assertEqual(rsp.status_code, 201)
rspJson = json.loads(rsp.text)
self.assertEqual(rspJson["linkCount"], link_count)
self.assertEqual(rspJson["attributeCount"], 0)
grp_id = rspJson["id"]
helper.validateId(grp_id)

# fetch all the links
req = helper.getEndpoint() + "/groups/" + grp_id + "/links"
rsp = self.session.get(req, headers=headers)
self.assertEqual(rsp.status_code, 200)
rspJson = json.loads(rsp.text)

self.assertTrue("links" in rspJson)
links_rsp = rspJson["links"]
self.assertEqual(len(links_rsp), link_count)
for i in range(link_count):
link_rsp = links_rsp[i]
self.assertTrue("class" in link_rsp)
self.assertEqual(link_rsp["class"], "H5L_TYPE_HARD")
self.assertTrue("id" in link_rsp)
self.assertTrue("title" in link_rsp)
self.assertEqual(link_rsp["title"], f"obj_{i}")
self.assertTrue("collection" in link_rsp)
self.assertEqual(link_rsp["collection"], "groups")
self.assertTrue("target" in link_rsp)
self.assertTrue("href" in link_rsp)

def testPostWithPath(self):
# test POST with implicit parent group creation
print("testPostWithPath", self.base_domain)
Expand Down
Loading