deltarepo: Implemented graph_from_links method of Graph class.
authorTomas Mlcoch <xtojaj@gmail.com>
Tue, 11 Feb 2014 20:12:07 +0000 (21:12 +0100)
committerTomas Mlcoch <xtojaj@gmail.com>
Tue, 11 Feb 2014 20:12:07 +0000 (21:12 +0100)
deltarepo/deltarepo/updater_common.py
deltarepo/managedeltarepos.py
deltarepo/tests/test_updater_common.py

index 477b6f2..d119699 100644 (file)
@@ -5,7 +5,7 @@ import librepo
 import tempfile
 import createrepo_c as cr
 from .deltarepos import DeltaRepos
-from .common import calculate_contenthash
+from .common import LoggingInterface, calculate_contenthash
 from .errors import DeltaRepoError
 
 class _Repo(object):
@@ -156,8 +156,8 @@ class Link(object):
     """
 
     def __init__(self):
-        self.deltareposrecord = None    # DeltaReposRecord()
-        self.drmirror = None            # DRMirror()
+        self._deltareposrecord = None    # DeltaReposRecord()
+        self._drmirror = None            # DRMirror()
 
     #def __getattr__(self, item):
     #    if hasattr(self.deltareposrecord, item):
@@ -167,17 +167,27 @@ class Link(object):
     @property
     def src(self):
         """Source content hash"""
-        return self.deltareposrecord.contenthash_src
+        return self._deltareposrecord.contenthash_src
 
     @property
     def dst(self):
         """Destination content hash."""
-        return self.deltareposrecord.contenthash_dst
+        return self._deltareposrecord.contenthash_dst
 
     @property
     def type(self):
         """Type of content hash (e.g., sha256, etc.) """
-        return self.deltareposrecord.contenthash_type
+        return self._deltareposrecord.contenthash_type
+
+    @property
+    def mirrorurl(self):
+        """Mirror url"""
+        return self._drmirror.url
+
+    def cost(self):
+        """Cost (currently just a total size).
+        In future maybe just sizes of needed delta metadata."""
+        return self._deltareposrecord.size_total
 
     @classmethod
     def links_from_drmirror(cls, drmirror):
@@ -189,7 +199,7 @@ class Link(object):
             links.append(link)
         return links
 
-class Solver(object):
+class Solver(LoggingInterface):
 
     class ResolvedPath(object):
         def __init__(self):
@@ -198,40 +208,85 @@ class Solver(object):
 
     class Node(object):
         """Single graph node"""
-        def __init__(self):
-            self.sources = []
-            self.targets = []
+        def __init__(self, value):
+            self.value = value   # Content hash
+            self.links = []      # List of all links that belong to the node
+                                 # All of them must have self.value as a src value
+            self.targets = {}    # { Node: Link }
+            self.sources = set() # set(Nodes)
+
+        def __repr__(self):
+            targets = [x.value for x in self.targets]
+            return "<Node {0} \'{1}\' points to: {2}>".format(
+                id(self), self.value, targets)
 
     class Graph(object):
-        def __init__(self):
-            self.links = []
-            self.nodes = []
+        def __init__(self, contenthash_type="sha256"):
+            #self.links = []
+            self.nodes = {}  # { 'content_hash': Node }
+            self.contenthash_type = contenthash_type
+
+        def get_node(self, contenthash):
+            return self.nodes.get(contenthash)
 
         @classmethod
-        def graph_from_links(cls, links):
-            nodes = []
-            # TODO
+        def graph_from_links(cls, links, logger, contenthash_type="sha256"):
+            already_processed_links = set() # Set of tuples (src, dst)
+            nodes = {}  # { 'content_hash': Node }
+
+            for link in links:
+                if contenthash_type != link.type.lower():
+                    logger.warning("Content hash type mishmash {0} vs {1}"
+                                   "".format(contenthash_type, link.type))
+
+                if (link.src, link.dst) in already_processed_links:
+                    logger.warning("Duplicated path {0}->{1} from {2} skipped"
+                                   "".format(link.src, link.dst, link.mirrorurl))
+                    continue
+
+                node = nodes.setdefault(link.src, Solver.Node(link.src))
+
+                if link.dst in node.targets:
+                    # Should not happen (the already_processed_links
+                    # list should avoid this)
+                    logger.warning("Duplicated path {0}->{1} from {2} skipped"
+                                   "".format(link.src, link.dst, link.mirrorurl))
+                    continue
+
+                #node.links.append(link)    # TODO: Remove (?)
+                dst_node = nodes.setdefault(link.dst, Solver.Node(link.dst))
+                dst_node.sources.add(node)
+                node.targets[dst_node] = link
+
             g = cls()
             g.links = links
             g.nodes = nodes
             return g
 
-    def __init__(self, links, start, target, contenthash_type="sha256"):
-        self.links = links      # List of hops
-        self.start = start      # Start hop
-        self.target = target    # Target hop
-        self.contenthash_type = contenthash_type
-
-        # TODO: process (split) DRMirror objects to PathHops
+    def __init__(self, links, source, target, contenthash_type="sha256", logger=None):
+        LoggingInterface.__init__(self, logger)
 
-    def get_paths(self):
-        """Get all available paths"""
-        # TODO: Raise warning when multiple hops with the same src
-        # and destination exists
-        pass
+        self.links = links      # Links
+        self.source_ch = source # Source content hash (str)
+        self.target_ch = target # Target content hash (str)
+        self.contenthash_type = contenthash_type
 
     def solve(self):
+        # Build the graph
+        graph = self.Graph.graph_from_links(self.links,
+                                            self.logger,
+                                            self.contenthash_type)
+
+        # Find start and end node in the graph
+        source_node = graph.get_node(self.source_ch)
+        if not source_node:
+            raise DeltaRepoError("Source repo ({0}) not available".format(self.source_ch))
+        target_node = graph.get_node(self.target_ch)
+        if not target_node:
+            raise DeltaRepoError("Target repo ({0}) not available".format(self.target_ch))
+
         # TODO: Implement Dijkstra's algorithm here
+
         pass
 
 class DRUpdater(object):
index 21834ec..d8a6253 100755 (executable)
@@ -281,3 +281,7 @@ if __name__ == "__main__":
         sys.exit(1)
 
     sys.exit(0)
+
+# TODO:
+# - Check for contenthash mishmashes
+# - Check for duplicated path (links)
index 1b9a94f..1342ca0 100644 (file)
@@ -1,6 +1,7 @@
 import os.path
+import logging
 import unittest
-from deltarepo.updater_common import LocalRepo, OriginRepo, DRMirror
+from deltarepo.updater_common import LocalRepo, OriginRepo, DRMirror, Solver
 
 from .fixtures import *
 
@@ -48,4 +49,52 @@ class TestCaseDRMirror(unittest.TestCase):
         drm = DRMirror.from_url(url)
         self.assertTrue(drm)
         self.assertEqual(len(drm.records), 3)
-        self.assertTrue(drm.deltarepos)
\ No newline at end of file
+        self.assertTrue(drm.deltarepos)
+
+class TestCaseSolver(unittest.TestCase):
+
+    class LinkMock(object):
+        def __init__(self, src, dst, type="sha256", mirrorurl="mockedlink", cost=100):
+            self.src = src
+            self.dst = dst
+            self.type = type
+            self.mirrorurl = mirrorurl
+            self._cost = cost
+
+        def cost(self):
+            return self._cost
+
+    def test_solver_graph_build(self):
+
+        links = []
+        links.append(TestCaseSolver.LinkMock("aaa", "bbb"))
+        links.append(TestCaseSolver.LinkMock("aaa", "ccc"))
+        links.append(TestCaseSolver.LinkMock("bbb", "ccc"))
+
+        logger = logging.getLogger("testloger")
+        graph = Solver.Graph().graph_from_links(links, logger)
+
+        self.assertTrue(graph)
+        self.assertEqual(len(graph.nodes), 3)
+        self.assertTrue("aaa" in graph.nodes)
+        self.assertTrue("bbb" in graph.nodes)
+        self.assertTrue("ccc" in graph.nodes)
+
+        self.assertEqual(len(graph.nodes["aaa"].targets), 2)
+        self.assertEqual(len(graph.nodes["bbb"].targets), 1)
+        self.assertEqual(len(graph.nodes["ccc"].targets), 0)
+
+        self.assertEqual(len(graph.nodes["aaa"].sources), 0)
+        self.assertEqual(len(graph.nodes["bbb"].sources), 1)
+        self.assertEqual(len(graph.nodes["ccc"].sources), 2)
+
+    def test_solver(self):
+
+        links = []
+        links.append(TestCaseSolver.LinkMock("aaa", "bbb"))
+        links.append(TestCaseSolver.LinkMock("aaa", "ccc"))
+        links.append(TestCaseSolver.LinkMock("bbb", "ccc"))
+
+        logger = logging.getLogger("testloger")
+        solver = Solver(links, "aaa", "bbb", logger=logger)
+        solver.solve()
\ No newline at end of file