From e6b5299db6311071db4a601175e267ab72b1a53a Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Sun, 10 Aug 2025 21:58:50 -0700 Subject: [PATCH] support a graph of mappings instead of just one layer... This isn't the most effecient for now as we generate all the possible mappings, but it's assumed that the number of mappings will be low for now... --- ui/fixtures/cmd.mapping.json | 20 +++++++++ ui/medashare/cli.py | 84 ++++++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/ui/fixtures/cmd.mapping.json b/ui/fixtures/cmd.mapping.json index 2f603c3..ef97d4a 100644 --- a/ui/fixtures/cmd.mapping.json +++ b/ui/fixtures/cmd.mapping.json @@ -103,5 +103,25 @@ "title": "just mapping lists them", "cmd": [ "mapping" ], "stdout_re": "^efdb5d9c-d123-4b30-aaa8-45a9ea8f6053:/.*subdir/mapa <-> ceaa4862-dd00-41ba-9787-7480ec1b2679:/.*subdir/mapb\n$" +}, +{ + "special": "set hostid", + "comment": "that a third host", + "hostid": "809c2a62-0df9-42a2-adae-c0a532ad860b" +}, +{ + "title": "that creating host c works", + "cmd": [ "hosts" ], + "format": [ "cmd" ] +}, +{ + "title": "that host mapping c works", + "cmd": [ "mapping", "--create", "mapc", "ceaa4862-dd00-41ba-9787-7480ec1b2679:{mappathb}" ], + "format": [ "cmd" ] +}, +{ + "title": "the search will pass through other mappings to get a local mapping", + "cmd": [ "search", "file", "+sometag=anothervalue" ], + "stdout_re": "mapc/text.txt\n$" } ] diff --git a/ui/medashare/cli.py b/ui/medashare/cli.py index bd49441..0a3cd25 100644 --- a/ui/medashare/cli.py +++ b/ui/medashare/cli.py @@ -647,13 +647,16 @@ class ObjectStore(object): '''Returns the tuple (lclpath, hostid, rempath) for all the mappings for this hostid.''' + # this is a little trick to return all possible mappings + # as we will have to explore all hosts that might have a + # mapping through us + hostid = _makeuuid(hostuuid()) sel = select(orm.MetaDataObject.data).where( - orm.HostMapping.hostid == hostid, - orm.HostMapping.objid == orm.MetaDataObject.uuid) + orm.MetaDataObject.type == 'mapping') - res = [] + allmappings = collections.defaultdict(lambda: []) with self._ses() as session: # XXX - view for obj in session.scalars(sel): @@ -661,11 +664,29 @@ class ObjectStore(object): pathlib.Path(b).resolve()))(*x.split(':', 1)) for x in obj.mapping ] for idx, (id, path) in enumerate(maps): - if hostid == id: - # add other to mapping - other = tuple(maps[(idx + 1) % - 2]) - res.append((path, ) + other) + other = tuple(maps[(idx + 1) % + 2]) + allmappings[id].append((path, ) + other) + + res = set(allmappings[hostid]) + tovisit = res.copy() + visited = set() + + while tovisit: + + lclpath, remhid, rempath = tovisit.pop() + + if (remhid, rempath) in visited: + continue + + visited.add((remhid, rempath)) + + addl = { (lclpath, x[1], x[2]) for x in + allmappings[remhid] if x[1] != hostid } + + res.update(addl) + tovisit.update(addl) + return res def by_file(self, fname, types=('metadata', )): @@ -2588,6 +2609,47 @@ class _TestCases(unittest.TestCase): # and that it can be verified persona.verify(mdobj) + def test_get_hostmapping(self): + # that an object store + persona = self.persona + objst = ObjectStore.load(self.tempdir / 'sample.data.sqlite3') + + with mock.patch(__name__ + '.hostuuid') as hostidpatch: + # with a local host uuid + hid = uuid.uuid4() + hostidpatch.return_value = hid + + host = persona.Host(name='lclhost', hostuuid=hid) + objst.loadobj(host) + + # and two other hosts: + hida = uuid.uuid4() + hidb = uuid.uuid4() + + objst.loadobj(persona.Host(name='hosta', + hostuuid=hida)) + objst.loadobj(persona.Host(name='hostb', + hostuuid=hidb)) + + # that when a mapping from lcl to a to b exists + objst.loadobj(persona.Mapping(mapping=[ + ':'.join((str(hid), '/lclpath')) , + ':'.join((str(hida), '/patha')) ])) + objst.loadobj(persona.Mapping(mapping=[ + ':'.join((str(hida), '/patha')) , + ':'.join((str(hidb), '/pathb')) ])) + + # that both a and b are included in the mappings + mappings = sorted(objst.get_hostmappings()) + + from pathlib import PosixPath + + lclpath = PosixPath('/lclpath') + self.assertEqual(mappings, sorted([ + (lclpath, hida, PosixPath('/patha')), + (lclpath, hidb, PosixPath('/pathb')) + ])) + def test_objectstore(self): persona = self.persona objst = ObjectStore.load(self.tempdir / 'sample.data.sqlite3') @@ -2797,11 +2859,17 @@ class _TestCases(unittest.TestCase): mappathb = self.tempdir / 'mapb' mappathb.mkdir() + mappathc = self.tempdir / 'mapc' + mappathc.mkdir() + filea = mappatha / 'text.txt' filea.write_text('abc123\n') fileb = mappathb / 'text.txt' + filec = mappathc / 'text.txt' shutil.copyfile(filea, fileb) shutil.copystat(filea, fileb) + shutil.copyfile(filea, filec) + shutil.copystat(filea, filec) elif special == 'delete files': for i in cmd['files']: os.unlink(i)