diff --git a/htools/Ganeti/HTools/Cluster.hs b/htools/Ganeti/HTools/Cluster.hs
index 52e8d33d389fe6eb5fc54d303120c2fa378f60b7..7984172f6ec4568ef9acb0b9617cf4d01ecac917 100644
--- a/htools/Ganeti/HTools/Cluster.hs
+++ b/htools/Ganeti/HTools/Cluster.hs
@@ -34,6 +34,7 @@ module Ganeti.HTools.Cluster
     , Table(..)
     , CStats(..)
     , AllocStats
+    , AllocResult
     , AllocMethod
     -- * Generic functions
     , totalResources
diff --git a/htools/Ganeti/HTools/Program/Hspace.hs b/htools/Ganeti/HTools/Program/Hspace.hs
index 5f409fec5aee28c26d2e106871f90f84e7b782a3..23c7aaf88ede7346365ccdb18b5322f762ef4d28 100644
--- a/htools/Ganeti/HTools/Program/Hspace.hs
+++ b/htools/Ganeti/HTools/Program/Hspace.hs
@@ -26,7 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 module Ganeti.HTools.Program.Hspace (main) where
 
 import Control.Monad
-import Data.Char (toUpper, isAlphaNum)
+import Data.Char (toUpper, isAlphaNum, toLower)
 import Data.Function (on)
 import Data.List
 import Data.Ord (comparing)
@@ -89,7 +89,7 @@ specPrefix SpecTiered = "TSPEC_INI"
 
 -- | The description of a spec.
 specDescription :: SpecType -> String
-specDescription SpecNormal = "Normal (fixed-size)"
+specDescription SpecNormal = "Standard (fixed-size)"
 specDescription SpecTiered = "Tiered (initial size)"
 
 -- | Efficiency generic function.
@@ -262,7 +262,7 @@ printAllocationMap :: Int -> String
                    -> Node.List -> [Instance.Instance] -> IO ()
 printAllocationMap verbose msg nl ixes =
   when (verbose > 1) $ do
-    hPutStrLn stderr msg
+    hPutStrLn stderr (msg ++ " map")
     hPutStr stderr . unlines . map ((:) ' ' .  intercalate " ") $
             formatTable (map (printInstance nl) (reverse ixes))
                         -- This is the numberic-or-not field
@@ -351,6 +351,35 @@ exitIfBad (Bad s) =
   hPrintf stderr "Failure: %s\n" s >> exitWith (ExitFailure 1)
 exitIfBad (Ok v) = return v
 
+-- | Runs an allocation algorithm and saves cluster state.
+runAllocation :: ClusterData                -- ^ Cluster data
+              -> Maybe Cluster.AllocResult  -- ^ Optional stop-allocation
+              -> Result Cluster.AllocResult -- ^ Allocation result
+              -> RSpec                      -- ^ Requested instance spec
+              -> SpecType                   -- ^ Allocation type
+              -> Options                    -- ^ CLI options
+              -> IO (FailStats, Node.List, Int, [(RSpec, Int)])
+runAllocation cdata stop_allocation actual_result spec mode opts = do
+  (reasons, new_nl, new_il, new_ixes, _) <-
+      case stop_allocation of
+        Just result_noalloc -> return result_noalloc
+        Nothing -> exitIfBad actual_result
+
+  let name = head . words . specDescription $ mode
+      descr = name ++ " allocation"
+      ldescr = "after " ++ map toLower descr
+
+  printISpec (optMachineReadable opts) spec mode (optDiskTemplate opts)
+
+  printAllocationMap (optVerbose opts) descr new_nl new_ixes
+
+  maybePrintNodes (optShowNodes opts) descr (Cluster.printNodes new_nl)
+
+  maybeSaveData (optSaveCluster opts) (map toLower name) ldescr
+                    (cdata { cdNodes = new_nl, cdInstances = new_il})
+
+  return (sortReasons reasons, new_nl, length new_ixes, tieredSpecMap new_ixes)
+
 -- | Main function.
 main :: IO ()
 main = do
@@ -363,49 +392,40 @@ main = do
 
   let verbose = optVerbose opts
       ispec = optISpec opts
-      shownodes = optShowNodes opts
       disk_template = optDiskTemplate opts
       req_nodes = Instance.requiredNodes disk_template
       machine_r = optMachineReadable opts
 
   (ClusterData gl fixed_nl il ctags) <- loadExternalData opts
+  nl <- setNodeStatus opts fixed_nl
 
   let num_instances = Container.size il
       all_nodes = Container.elems fixed_nl
-      m_cpu = optMcpu opts
+      cdata = ClusterData gl nl il ctags
       csf = commonSuffix fixed_nl il
 
-  nl <- setNodeStatus opts fixed_nl
-
   when (not (null csf) && verbose > 1) $
        hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
 
-  maybePrintNodes shownodes "Initial cluster" (Cluster.printNodes nl)
-
-  let ini_cv = Cluster.compCV nl
-      ini_stats = Cluster.totalResources nl
+  maybePrintNodes (optShowNodes opts) "Initial cluster" (Cluster.printNodes nl)
 
   when (verbose > 2) $
          hPrintf stderr "Initial coefficients: overall %.8f, %s\n"
-                 ini_cv (Cluster.printStats nl)
+                 (Cluster.compCV nl) (Cluster.printStats nl)
 
-  printCluster machine_r ini_stats (length all_nodes)
+  printCluster machine_r (Cluster.totalResources nl) (length all_nodes)
 
-  printISpec machine_r ispec SpecNormal disk_template
-
-  let bad_nodes = fst $ Cluster.computeBadItems nl il
-      stop_allocation = not $ null bad_nodes
-      result_noalloc = ([(FailN1, 1)]::FailStats, nl, il, [], [])
+  let stop_allocation = case Cluster.computeBadItems nl il of
+                          ([], _) -> Nothing
+                          _ -> Just ([(FailN1, 1)]::FailStats, nl, il, [], [])
+      alloclimit = if optMaxLength opts == -1
+                   then Nothing
+                   else Just (optMaxLength opts)
 
   -- utility functions
   let iofspec spx = Instance.create "new" (rspecMem spx) (rspecDsk spx)
                     (rspecCpu spx) "running" [] True (-1) (-1) disk_template
 
-  let reqinst = iofspec ispec
-      alloclimit = if optMaxLength opts == -1
-                   then Nothing
-                   else Just (optMaxLength opts)
-
   allocnodes <- exitIfBad $ Cluster.genAllocNodes gl nl req_nodes True
 
   -- Run the tiered allocation, if enabled
@@ -413,44 +433,20 @@ main = do
   (case optTieredSpec opts of
      Nothing -> return ()
      Just tspec -> do
-       (treason, trl_nl, trl_il, trl_ixes, _) <-
-           if stop_allocation
-           then return result_noalloc
-           else exitIfBad (Cluster.tieredAlloc nl il alloclimit (iofspec tspec)
-                                  allocnodes [] [])
-       let spec_map' = tieredSpecMap trl_ixes
-           treason' = sortReasons treason
-
-       printAllocationMap verbose "Tiered allocation map" trl_nl trl_ixes
-
-       maybePrintNodes shownodes "Tiered allocation"
-                           (Cluster.printNodes trl_nl)
+       (treason, trl_nl, _, spec_map) <-
+           runAllocation cdata stop_allocation
+                   (Cluster.tieredAlloc nl il alloclimit (iofspec tspec)
+                           allocnodes [] []) tspec SpecTiered opts
 
-       maybeSaveData (optSaveCluster opts) "tiered" "after tiered allocation"
-                     (ClusterData gl trl_nl trl_il ctags)
-
-       printISpec machine_r tspec SpecTiered disk_template
-
-       printTiered machine_r spec_map' m_cpu nl trl_nl treason'
+       printTiered machine_r spec_map (optMcpu opts) nl trl_nl treason
        )
 
   -- Run the standard (avg-mode) allocation
 
-  (ereason, fin_nl, fin_il, ixes, _) <-
-      if stop_allocation
-      then return result_noalloc
-      else exitIfBad (Cluster.iterateAlloc nl il alloclimit
-                      reqinst allocnodes [] [])
-
-  let allocs = length ixes
-      sreason = sortReasons ereason
-
-  printAllocationMap verbose "Standard allocation map" fin_nl ixes
-
-  maybePrintNodes shownodes "Standard allocation" (Cluster.printNodes fin_nl)
-
-  maybeSaveData (optSaveCluster opts) "alloc" "after standard allocation"
-       (ClusterData gl fin_nl fin_il ctags)
+  (sreason, fin_nl, allocs, _) <-
+      runAllocation cdata stop_allocation
+            (Cluster.iterateAlloc nl il alloclimit (iofspec ispec)
+             allocnodes [] []) ispec SpecNormal opts
 
   printResults machine_r nl fin_nl num_instances allocs sreason