diff --git a/htest/Test/Ganeti/HTools/Node.hs b/htest/Test/Ganeti/HTools/Node.hs
index 2e1acf51687d007113b80046fd7238032419021c..4b36db4a5da6749eb27bcc874cf526aba94553ab 100644
--- a/htest/Test/Ganeti/HTools/Node.hs
+++ b/htest/Test/Ganeti/HTools/Node.hs
@@ -36,15 +36,19 @@ module Test.Ganeti.HTools.Node
   ) where
 
 import Test.QuickCheck
+import Test.HUnit
 
 import Control.Monad
 import qualified Data.Map as Map
+import qualified Data.Graph as Graph
 import Data.List
 
 import Test.Ganeti.TestHelper
 import Test.Ganeti.TestCommon
 import Test.Ganeti.TestHTools
-import Test.Ganeti.HTools.Instance (genInstanceSmallerThanNode)
+import Test.Ganeti.HTools.Instance ( genInstanceSmallerThanNode
+                                   , genInstanceList
+                                   , genInstanceOnNodeList)
 
 import Ganeti.BasicTypes
 import qualified Ganeti.HTools.Loader as Loader
@@ -52,6 +56,9 @@ import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Instance as Instance
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.Types as Types
+import qualified Ganeti.HTools.Graph as HGraph
+
+{-# ANN module "HLint: ignore Use camelCase" #-}
 
 -- * Arbitrary instances
 
@@ -107,6 +114,14 @@ genNodeList :: Gen Node.Node -> Gen Node.List
 genNodeList ngen = fmap (snd . Loader.assignIndices) names_nodes
     where names_nodes = (fmap . map) (\n -> (Node.name n, n)) $ listOf1 ngen
 
+-- | Generate a node list, an instance list, and a node graph.
+-- We choose instances with nodes contained in the node list.
+genNodeGraph :: Gen (Maybe Graph.Graph, Node.List, Instance.List)
+genNodeGraph = do
+  nl <- genNodeList genOnlineNode `suchThat` ((2<=).Container.size)
+  il <- genInstanceList (genInstanceOnNodeList nl)
+  return (Node.mkNodeGraph nl il, nl, il)
+
 -- * Test cases
 
 prop_setAlias :: Node.Node -> String -> Bool
@@ -316,6 +331,56 @@ prop_addSec_idempotent =
        Ok node' -> Node.removeSec node' inst'' ==? node
        _ -> failTest "Can't add instance"
 
+-- | Check that no graph is created on an empty node list.
+case_emptyNodeList :: Assertion
+case_emptyNodeList =
+  assertEqual "" Nothing $ Node.mkNodeGraph emptynodes emptyinstances
+    where emptynodes = Container.empty :: Node.List
+          emptyinstances = Container.empty :: Instance.List
+
+-- | Check that the number of vertices of a nodegraph is equal to the number of
+-- nodes in the original node list.
+prop_numVertices :: Property
+prop_numVertices =
+  forAll genNodeGraph $ \(graph, nl, _) ->
+    (fmap numvertices graph ==? Just (Container.size nl))
+    where numvertices = length . Graph.vertices
+
+-- | Check that the number of edges of a nodegraph is equal to twice the number
+-- of instances with secondary nodes in the original instance list.
+prop_numEdges :: Property
+prop_numEdges =
+  forAll genNodeGraph $ \(graph, _, il) ->
+    (fmap numedges graph ==? Just (numwithsec il * 2))
+    where numedges = length . Graph.edges
+          numwithsec = length . filter Instance.hasSecondary . Container.elems
+
+-- | Check that a node graph is colorable.
+prop_nodeGraphIsColorable :: Property
+prop_nodeGraphIsColorable =
+  forAll genNodeGraph $ \(graph, _, _) ->
+    fmap HGraph.isColorable graph ==? Just True
+
+-- | Check that each edge in a nodegraph is an instance.
+prop_instanceIsEdge :: Property
+prop_instanceIsEdge =
+  forAll genNodeGraph $ \(graph, _, il) ->
+    fmap (\g -> all (`isEdgeOn` g) (iwithsec il)) graph ==? Just True
+    where i `isEdgeOn` g = iEdges i `intersect` Graph.edges g == iEdges i
+          iEdges i = [ (Instance.pNode i, Instance.sNode i)
+                     , (Instance.sNode i, Instance.pNode i)]
+          iwithsec = filter Instance.hasSecondary . Container.elems
+
+-- | Check that each instance in an edge in the resulting nodegraph.
+prop_edgeIsInstance :: Property
+prop_edgeIsInstance =
+  forAll genNodeGraph $ \(graph, _, il) ->
+    fmap (all (`isInstanceIn` il).Graph.edges) graph ==? Just True
+      where e `isInstanceIn` il = any (`hasNodes` e) (Container.elems il)
+            i `hasNodes` (v1,v2) =
+              Instance.allNodes i `elem` permutations [v1,v2]
+
+-- | List of tests for the Node module.
 testSuite "HTools/Node"
             [ 'prop_setAlias
             , 'prop_setOffline
@@ -338,4 +403,10 @@ testSuite "HTools/Node"
             , 'prop_computeGroups
             , 'prop_addPri_idempotent
             , 'prop_addSec_idempotent
+            , 'case_emptyNodeList
+            , 'prop_numVertices
+            , 'prop_numEdges
+            , 'prop_nodeGraphIsColorable
+            , 'prop_edgeIsInstance
+            , 'prop_instanceIsEdge
             ]
diff --git a/htools/Ganeti/HTools/Node.hs b/htools/Ganeti/HTools/Node.hs
index 6dd85a9d84cd15b2f16312460fb0887688f1887f..24534556db26c0e301879e1676ea6c819c01981d 100644
--- a/htools/Ganeti/HTools/Node.hs
+++ b/htools/Ganeti/HTools/Node.hs
@@ -70,13 +70,17 @@ module Ganeti.HTools.Node
   , AllocElement
   , noSecondary
   , computeGroups
+  , mkNodeGraph
   ) where
 
 import Data.List hiding (group)
 import qualified Data.Map as Map
 import qualified Data.Foldable as Foldable
+import qualified Data.IntMap as IntMap
+import qualified Data.Graph as Graph
 import Data.Ord (comparing)
 import Text.Printf (printf)
+import Control.Monad (liftM, liftM2)
 
 import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Instance as Instance
@@ -544,6 +548,38 @@ availCpu t =
 iMem :: Node -> Int
 iMem t = truncate (tMem t) - nMem t - xMem t - fMem t
 
+-- * Node graph functions
+-- These functions do the transformations needed so that nodes can be
+-- represented as a graph connected by the instances that are replicated
+-- on them.
+
+-- * Making of a Graph from a node/instance list
+
+-- | Transform an instance into a list of edges on the node graph
+instanceToEdges :: Instance.Instance -> [Graph.Edge]
+instanceToEdges i
+  | Instance.hasSecondary i = [(pnode,snode), (snode,pnode)]
+  | otherwise = []
+    where pnode = Instance.pNode i
+          snode = Instance.sNode i
+
+-- | Transform the list of instances into list of destination edges
+instancesToEdges :: Instance.List -> [Graph.Edge]
+instancesToEdges = concatMap instanceToEdges . Container.elems
+
+-- | Transform the list of nodes into vertices bounds.
+-- Returns Nothing is the list is empty.
+nodesToBounds :: List -> Maybe Graph.Bounds
+nodesToBounds nl = liftM2 (,) nmin nmax
+    where nmin = fmap (fst . fst) (IntMap.minViewWithKey nl)
+          nmax = fmap (fst . fst) (IntMap.maxViewWithKey nl)
+
+-- | Transform a Node + Instance list into a NodeGraph type.
+-- Returns Nothing if the node list is empty.
+mkNodeGraph :: List -> Instance.List -> Maybe Graph.Graph
+mkNodeGraph nl il =
+  liftM (`Graph.buildG` instancesToEdges il) (nodesToBounds nl)
+
 -- * Display functions
 
 -- | Return a field for a given node.