Rename Ganeti/HTools/Utils.hs to Ganeti/Utils.hs
[ganeti-local] / htools / Ganeti / HTools / Program / Hspace.hs
index b882bfc..605b4ff 100644 (file)
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -23,16 +23,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 -}
 
-module Ganeti.HTools.Program.Hspace (main) where
+module Ganeti.HTools.Program.Hspace (main, options) where
 
 import Control.Monad
-import Data.Char (toUpper, isAlphaNum)
+import Data.Char (toUpper, toLower)
+import Data.Function (on)
 import Data.List
-import Data.Maybe (isJust, fromJust)
+import Data.Maybe (fromMaybe)
 import Data.Ord (comparing)
-import System (exitWith, ExitCode(..))
 import System.IO
-import qualified System
 
 import Text.Printf (printf, hPrintf)
 
@@ -41,34 +40,34 @@ import qualified Ganeti.HTools.Cluster as Cluster
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.Instance as Instance
 
-import Ganeti.HTools.Utils
 import Ganeti.HTools.Types
 import Ganeti.HTools.CLI
 import Ganeti.HTools.ExtLoader
-import Ganeti.HTools.Loader (ClusterData(..))
+import Ganeti.HTools.Loader
+import Ganeti.Utils
 
--- | Options list and functions
+-- | Options list and functions.
 options :: [OptType]
 options =
-    [ oPrintNodes
-    , oDataFile
-    , oDiskTemplate
-    , oNodeSim
-    , oRapiMaster
-    , oLuxiSocket
-    , oVerbose
-    , oQuiet
-    , oOfflineNode
-    , oIMem
-    , oIDisk
-    , oIVcpus
-    , oMaxCpu
-    , oMinDisk
-    , oTieredSpec
-    , oSaveCluster
-    , oShowVer
-    , oShowHelp
-    ]
+  [ oPrintNodes
+  , oDataFile
+  , oDiskTemplate
+  , oSpindleUse
+  , oNodeSim
+  , oRapiMaster
+  , oLuxiSocket
+  , oIAllocSrc
+  , oVerbose
+  , oQuiet
+  , oOfflineNode
+  , oMachineReadable
+  , oMaxCpu
+  , oMaxSolLength
+  , oMinDisk
+  , oStdSpec
+  , oTieredSpec
+  , oSaveCluster
+  ]
 
 -- | The allocation phase we're in (initial, after tiered allocs, or
 -- after regular allocation).
@@ -76,6 +75,44 @@ data Phase = PInitial
            | PFinal
            | PTiered
 
+-- | The kind of instance spec we print.
+data SpecType = SpecNormal
+              | SpecTiered
+
+-- | Prefix for machine readable names
+htsPrefix :: String
+htsPrefix = "HTS"
+
+-- | What we prefix a spec with.
+specPrefix :: SpecType -> String
+specPrefix SpecNormal = "SPEC"
+specPrefix SpecTiered = "TSPEC_INI"
+
+-- | The description of a spec.
+specDescription :: SpecType -> String
+specDescription SpecNormal = "Standard (fixed-size)"
+specDescription SpecTiered = "Tiered (initial size)"
+
+-- | Efficiency generic function.
+effFn :: (Cluster.CStats -> Integer)
+      -> (Cluster.CStats -> Double)
+      -> Cluster.CStats -> Double
+effFn fi ft cs = fromIntegral (fi cs) / ft cs
+
+-- | Memory efficiency.
+memEff :: Cluster.CStats -> Double
+memEff = effFn Cluster.csImem Cluster.csTmem
+
+-- | Disk efficiency.
+dskEff :: Cluster.CStats -> Double
+dskEff = effFn Cluster.csIdsk Cluster.csTdsk
+
+-- | Cpu efficiency.
+cpuEff :: Cluster.CStats -> Double
+cpuEff = effFn Cluster.csIcpu (fromIntegral . Cluster.csVcpu)
+
+-- | Holds data for converting a 'Cluster.CStats' structure into
+-- detailed statictics.
 statsData :: [(String, Cluster.CStats -> String)]
 statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
             , ("INST_CNT", printf "%d" . Cluster.csNinst)
@@ -86,31 +123,27 @@ statsData = [ ("SCORE", printf "%.8f" . Cluster.csScore)
             , ("MEM_INST", printf "%d" . Cluster.csImem)
             , ("MEM_OVERHEAD",
                \cs -> printf "%d" (Cluster.csXmem cs + Cluster.csNmem cs))
-            , ("MEM_EFF",
-               \cs -> printf "%.8f" (fromIntegral (Cluster.csImem cs) /
-                                     Cluster.csTmem cs))
+            , ("MEM_EFF", printf "%.8f" . memEff)
             , ("DSK_FREE", printf "%d" . Cluster.csFdsk)
             , ("DSK_AVAIL", printf "%d". Cluster.csAdsk)
             , ("DSK_RESVD",
                \cs -> printf "%d" (Cluster.csFdsk cs - Cluster.csAdsk cs))
             , ("DSK_INST", printf "%d" . Cluster.csIdsk)
-            , ("DSK_EFF",
-               \cs -> printf "%.8f" (fromIntegral (Cluster.csIdsk cs) /
-                                    Cluster.csTdsk cs))
+            , ("DSK_EFF", printf "%.8f" . dskEff)
             , ("CPU_INST", printf "%d" . Cluster.csIcpu)
-            , ("CPU_EFF",
-               \cs -> printf "%.8f" (fromIntegral (Cluster.csIcpu cs) /
-                                     Cluster.csTcpu cs))
+            , ("CPU_EFF", printf "%.8f" . cpuEff)
             , ("MNODE_MEM_AVAIL", printf "%d" . Cluster.csMmem)
             , ("MNODE_DSK_AVAIL", printf "%d" . Cluster.csMdsk)
             ]
 
+-- | List holding 'RSpec' formatting information.
 specData :: [(String, RSpec -> String)]
 specData = [ ("MEM", printf "%d" . rspecMem)
            , ("DSK", printf "%d" . rspecDsk)
            , ("CPU", printf "%d" . rspecCpu)
            ]
 
+-- | List holding 'Cluster.CStats' formatting information.
 clusterData :: [(String, Cluster.CStats -> String)]
 clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
               , ("DSK", printf "%.0f" . Cluster.csTdsk)
@@ -118,7 +151,7 @@ clusterData = [ ("MEM", printf "%.0f" . Cluster.csTmem)
               , ("VCPU", printf "%d" . Cluster.csVcpu)
               ]
 
--- | Function to print stats for a given phase
+-- | Function to print stats for a given phase.
 printStats :: Phase -> Cluster.CStats -> [(String, String)]
 printStats ph cs =
   map (\(s, fn) -> (printf "%s_%s" kind s, fn cs)) statsData
@@ -127,59 +160,85 @@ printStats ph cs =
                  PFinal -> "FIN"
                  PTiered -> "TRL"
 
--- | Print final stats and related metrics
-printResults :: Node.List -> Int -> Int -> [(FailMode, Int)] -> IO ()
-printResults fin_nl num_instances allocs sreason = do
+-- | Print failure reason and scores
+printFRScores :: Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
+printFRScores ini_nl fin_nl sreason = do
+  printf "  - most likely failure reason: %s\n" $ failureReason sreason::IO ()
+  printClusterScores ini_nl fin_nl
+  printClusterEff (Cluster.totalResources fin_nl)
+
+-- | Print final stats and related metrics.
+printResults :: Bool -> Node.List -> Node.List -> Int -> Int
+             -> [(FailMode, Int)] -> IO ()
+printResults True _ fin_nl num_instances allocs sreason = do
   let fin_stats = Cluster.totalResources fin_nl
       fin_instances = num_instances + allocs
 
-  when (num_instances + allocs /= Cluster.csNinst fin_stats) $
-       do
-         hPrintf stderr "ERROR: internal inconsistency, allocated (%d)\
-                        \ != counted (%d)\n" (num_instances + allocs)
-                                 (Cluster.csNinst fin_stats) :: IO ()
-         exitWith $ ExitFailure 1
-
-  printKeys $ printStats PFinal fin_stats
-  printKeys [ ("ALLOC_USAGE", printf "%.8f"
-                                ((fromIntegral num_instances::Double) /
-                                 fromIntegral fin_instances))
-            , ("ALLOC_INSTANCES", printf "%d" allocs)
-            , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
-            ]
-  printKeys $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
-                               printf "%d" y)) sreason
-  -- this should be the final entry
-  printKeys [("OK", "1")]
-
-formatRSpec :: Double -> String -> RSpec -> [(String, String)]
-formatRSpec m_cpu s r =
-    [ ("KM_" ++ s ++ "_CPU", show $ rspecCpu r)
-    , ("KM_" ++ s ++ "_NPU", show $ fromIntegral (rspecCpu r) / m_cpu)
-    , ("KM_" ++ s ++ "_MEM", show $ rspecMem r)
-    , ("KM_" ++ s ++ "_DSK", show $ rspecDsk r)
-    ]
-
-printAllocationStats :: Double -> Node.List -> Node.List -> IO ()
-printAllocationStats m_cpu ini_nl fin_nl = do
+  exitWhen (num_instances + allocs /= Cluster.csNinst fin_stats) $
+           printf "internal inconsistency, allocated (%d)\
+                  \ != counted (%d)\n" (num_instances + allocs)
+           (Cluster.csNinst fin_stats)
+
+  printKeysHTS $ printStats PFinal fin_stats
+  printKeysHTS [ ("ALLOC_USAGE", printf "%.8f"
+                                   ((fromIntegral num_instances::Double) /
+                                   fromIntegral fin_instances))
+               , ("ALLOC_INSTANCES", printf "%d" allocs)
+               , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
+               ]
+  printKeysHTS $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
+                                  printf "%d" y)) sreason
+
+printResults False ini_nl fin_nl _ allocs sreason = do
+  putStrLn "Normal (fixed-size) allocation results:"
+  printf "  - %3d instances allocated\n" allocs :: IO ()
+  printFRScores ini_nl fin_nl sreason
+
+-- | Prints the final @OK@ marker in machine readable output.
+printFinalHTS :: Bool -> IO ()
+printFinalHTS = printFinal htsPrefix
+
+-- | Compute the tiered spec counts from a list of allocated
+-- instances.
+tieredSpecMap :: [Instance.Instance]
+              -> [(RSpec, Int)]
+tieredSpecMap trl_ixes =
+  let fin_trl_ixes = reverse trl_ixes
+      ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
+      spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
+                 ix_byspec
+  in spec_map
+
+-- | Formats a spec map to strings.
+formatSpecMap :: [(RSpec, Int)] -> [String]
+formatSpecMap =
+  map (\(spec, cnt) -> printf "%d,%d,%d=%d" (rspecMem spec)
+                       (rspecDsk spec) (rspecCpu spec) cnt)
+
+-- | Formats \"key-metrics\" values.
+formatRSpec :: String -> AllocInfo -> [(String, String)]
+formatRSpec s r =
+  [ ("KM_" ++ s ++ "_CPU", show $ allocInfoVCpus r)
+  , ("KM_" ++ s ++ "_NPU", show $ allocInfoNCpus r)
+  , ("KM_" ++ s ++ "_MEM", show $ allocInfoMem r)
+  , ("KM_" ++ s ++ "_DSK", show $ allocInfoDisk r)
+  ]
+
+-- | Shows allocations stats.
+printAllocationStats :: Node.List -> Node.List -> IO ()
+printAllocationStats ini_nl fin_nl = do
   let ini_stats = Cluster.totalResources ini_nl
       fin_stats = Cluster.totalResources fin_nl
       (rini, ralo, runa) = Cluster.computeAllocationDelta ini_stats fin_stats
-  printKeys $ formatRSpec m_cpu  "USED" rini
-  printKeys $ formatRSpec m_cpu "POOL"ralo
-  printKeys $ formatRSpec m_cpu "UNAV" runa
+  printKeysHTS $ formatRSpec "USED" rini
+  printKeysHTS $ formatRSpec "POOL" ralo
+  printKeysHTS $ formatRSpec "UNAV" runa
 
--- | Ensure a value is quoted if needed
-ensureQuoted :: String -> String
-ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
-                 then '\'':v ++ "'"
-                 else v
-
--- | Format a list of key\/values as a shell fragment
-printKeys :: [(String, String)] -> IO ()
-printKeys = mapM_ (\(k, v) ->
-                   printf "HTS_%s=%s\n" (map toUpper k) (ensureQuoted v))
+-- | Format a list of key\/values as a shell fragment.
+printKeysHTS :: [(String, String)] -> IO ()
+printKeysHTS = printKeys htsPrefix
 
+-- | Converts instance data to a list of strings.
 printInstance :: Node.List -> Instance.Instance -> [String]
 printInstance nl i = [ Instance.name i
                      , Container.nameOf nl $ Instance.pNode i
@@ -191,150 +250,202 @@ printInstance nl i = [ Instance.name i
                      , show (Instance.vcpus i)
                      ]
 
--- | Optionally print the allocation map
+-- | Optionally print the allocation map.
 printAllocationMap :: Int -> String
                    -> Node.List -> [Instance.Instance] -> IO ()
 printAllocationMap verbose msg nl ixes =
   when (verbose > 1) $ do
-    hPutStrLn stderr msg
-    hPutStr stderr . unlines . map ((:) ' ' .  intercalate " ") $
+    hPutStrLn stderr (msg ++ " map")
+    hPutStr stderr . unlines . map ((:) ' ' .  unwords) $
             formatTable (map (printInstance nl) (reverse ixes))
                         -- This is the numberic-or-not field
                         -- specification; the first three fields are
                         -- strings, whereas the rest are numeric
                        [False, False, False, True, True, True]
 
--- | Main function.
-main :: IO ()
-main = do
-  cmd_args <- System.getArgs
-  (opts, args) <- parseOpts cmd_args "hspace" options
+-- | Formats nicely a list of resources.
+formatResources :: a -> [(String, a->String)] -> String
+formatResources res =
+    intercalate ", " . map (\(a, fn) -> a ++ " " ++ fn res)
+
+-- | Print the cluster resources.
+printCluster :: Bool -> Cluster.CStats -> Int -> IO ()
+printCluster True ini_stats node_count = do
+  printKeysHTS $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
+  printKeysHTS [("CLUSTER_NODES", printf "%d" node_count)]
+  printKeysHTS $ printStats PInitial ini_stats
+
+printCluster False ini_stats node_count = do
+  printf "The cluster has %d nodes and the following resources:\n  %s.\n"
+         node_count (formatResources ini_stats clusterData)::IO ()
+  printf "There are %s initial instances on the cluster.\n"
+             (if inst_count > 0 then show inst_count else "no" )
+      where inst_count = Cluster.csNinst ini_stats
+
+-- | Prints the normal instance spec.
+printISpec :: Bool -> RSpec -> SpecType -> DiskTemplate -> IO ()
+printISpec True ispec spec disk_template = do
+  printKeysHTS $ map (\(a, fn) -> (prefix ++ "_" ++ a, fn ispec)) specData
+  printKeysHTS [ (prefix ++ "_RQN", printf "%d" req_nodes) ]
+  printKeysHTS [ (prefix ++ "_DISK_TEMPLATE",
+                  diskTemplateToRaw disk_template) ]
+      where req_nodes = Instance.requiredNodes disk_template
+            prefix = specPrefix spec
+
+printISpec False ispec spec disk_template =
+  printf "%s instance spec is:\n  %s, using disk\
+         \ template '%s'.\n"
+         (specDescription spec)
+         (formatResources ispec specData) (diskTemplateToRaw disk_template)
+
+-- | Prints the tiered results.
+printTiered :: Bool -> [(RSpec, Int)]
+            -> Node.List -> Node.List -> [(FailMode, Int)] -> IO ()
+printTiered True spec_map nl trl_nl _ = do
+  printKeysHTS $ printStats PTiered (Cluster.totalResources trl_nl)
+  printKeysHTS [("TSPEC", unwords (formatSpecMap spec_map))]
+  printAllocationStats nl trl_nl
+
+printTiered False spec_map ini_nl fin_nl sreason = do
+  _ <- printf "Tiered allocation results:\n"
+  if null spec_map
+    then putStrLn "  - no instances allocated"
+    else mapM_ (\(ispec, cnt) ->
+                  printf "  - %3d instances of spec %s\n" cnt
+                           (formatResources ispec specData)) spec_map
+  printFRScores ini_nl fin_nl sreason
+
+-- | Displays the initial/final cluster scores.
+printClusterScores :: Node.List -> Node.List -> IO ()
+printClusterScores ini_nl fin_nl = do
+  printf "  - initial cluster score: %.8f\n" $ Cluster.compCV ini_nl::IO ()
+  printf "  -   final cluster score: %.8f\n" $ Cluster.compCV fin_nl
+
+-- | Displays the cluster efficiency.
+printClusterEff :: Cluster.CStats -> IO ()
+printClusterEff cs =
+  mapM_ (\(s, fn) ->
+           printf "  - %s usage efficiency: %5.2f%%\n" s (fn cs * 100))
+          [("memory", memEff),
+           ("  disk", dskEff),
+           ("  vcpu", cpuEff)]
+
+-- | Computes the most likely failure reason.
+failureReason :: [(FailMode, Int)] -> String
+failureReason = show . fst . head
+
+-- | Sorts the failure reasons.
+sortReasons :: [(FailMode, Int)] -> [(FailMode, Int)]
+sortReasons = reverse . sortBy (comparing snd)
+
+-- | 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
+              -> DiskTemplate               -- ^ Requested disk template
+              -> SpecType                   -- ^ Allocation type
+              -> Options                    -- ^ CLI options
+              -> IO (FailStats, Node.List, Int, [(RSpec, Int)])
+runAllocation cdata stop_allocation actual_result spec dt mode opts = do
+  (reasons, new_nl, new_il, new_ixes, _) <-
+      case stop_allocation of
+        Just result_noalloc -> return result_noalloc
+        Nothing -> exitIfBad "failure during allocation" actual_result
+
+  let name = head . words . specDescription $ mode
+      descr = name ++ " allocation"
+      ldescr = "after " ++ map toLower descr
+
+  printISpec (optMachineReadable opts) spec mode dt
+
+  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)
+
+-- | Create an instance from a given spec.
+instFromSpec :: RSpec -> DiskTemplate -> Int -> Instance.Instance
+instFromSpec spx =
+  Instance.create "new" (rspecMem spx) (rspecDsk spx)
+    (rspecCpu spx) Running [] True (-1) (-1)
 
-  unless (null args) $ do
-         hPutStrLn stderr "Error: this program doesn't take any arguments."
-         exitWith $ ExitFailure 1
+-- | Main function.
+main :: Options -> [String] -> IO ()
+main opts args = do
+  exitUnless (null args) "this program doesn't take any arguments"
 
   let verbose = optVerbose opts
-      ispec = optISpec opts
-      shownodes = optShowNodes opts
-      disk_template = optDiskTemplate opts
-      req_nodes = Instance.requiredNodes disk_template
-
-  (ClusterData gl fixed_nl il ctags) <- loadExternalData opts
+      machine_r = optMachineReadable opts
 
-  printKeys $ map (\(a, fn) -> ("SPEC_" ++ a, fn ispec)) specData
-  printKeys [ ("SPEC_RQN", printf "%d" req_nodes) ]
-  printKeys [ ("SPEC_DISK_TEMPLATE", dtToString disk_template) ]
+  orig_cdata@(ClusterData gl fixed_nl il _ ipol) <- loadExternalData opts
+  nl <- setNodeStatus opts fixed_nl
 
-  let num_instances = length $ Container.elems il
+  cluster_disk_template <-
+    case iPolicyDiskTemplates ipol of
+      first_templ:_ -> return first_templ
+      _ -> exitErr "null list of disk templates received from cluster"
 
-  let offline_names = optOffline opts
+  let num_instances = Container.size il
       all_nodes = Container.elems fixed_nl
-      all_names = map Node.name all_nodes
-      offline_wrong = filter (`notElem` all_names) offline_names
-      offline_indices = map Node.idx $
-                        filter (\n ->
-                                 Node.name n `elem` offline_names ||
-                                 Node.alias n `elem` offline_names)
-                               all_nodes
-      m_cpu = optMcpu opts
-      m_dsk = optMdsk opts
-
-  when (length offline_wrong > 0) $ do
-         hPrintf stderr "Error: Wrong node name(s) set as offline: %s\n"
-                     (commaJoin offline_wrong) :: IO ()
-         exitWith $ ExitFailure 1
-
-  when (req_nodes /= 1 && req_nodes /= 2) $ do
-         hPrintf stderr "Error: Invalid required nodes (%d)\n"
-                                            req_nodes :: IO ()
-         exitWith $ ExitFailure 1
-
-  let nm = Container.map (\n -> if Node.idx n `elem` offline_indices
-                                then Node.setOffline n True
-                                else n) fixed_nl
-      nl = Container.map (flip Node.setMdsk m_dsk . flip Node.setMcpu m_cpu)
-           nm
+      cdata = orig_cdata { cdNodes = fixed_nl }
+      disk_template = fromMaybe cluster_disk_template (optDiskTemplate opts)
+      req_nodes = Instance.requiredNodes disk_template
       csf = commonSuffix fixed_nl il
+      su = fromMaybe (iSpecSpindleUse $ iPolicyStdSpec ipol)
+                     (optSpindleUse opts)
 
-  when (length csf > 0 && verbose > 1) $
+  when (not (null csf) && verbose > 1) $
        hPrintf stderr "Note: Stripping common suffix of '%s' from names\n" csf
 
-  when (isJust shownodes) $
-       do
-         hPutStrLn stderr "Initial cluster status:"
-         hPutStrLn stderr $ Cluster.printNodes nl (fromJust shownodes)
-
-  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)
-
-  printKeys $ map (\(a, fn) -> ("CLUSTER_" ++ a, fn ini_stats)) clusterData
-  printKeys [("CLUSTER_NODES", printf "%d" (length all_nodes))]
-  printKeys $ printStats PInitial ini_stats
+         hPrintf stderr "Initial coefficients: overall %.8f\n%s"
+                 (Cluster.compCV nl) (Cluster.printStats "  " nl)
 
-  let bad_nodes = fst $ Cluster.computeBadItems nl il
-      stop_allocation = length bad_nodes > 0
-      result_noalloc = ([(FailN1, 1)]::FailStats, nl, il, [], [])
+  printCluster machine_r (Cluster.totalResources nl) (length all_nodes)
 
-  -- utility functions
-  let iofspec spx = Instance.create "new" (rspecMem spx) (rspecDsk spx)
-                    (rspecCpu spx) "running" [] True (-1) (-1) disk_template
-      exitifbad val = (case val of
-                         Bad s -> do
-                           hPrintf stderr "Failure: %s\n" s :: IO ()
-                           exitWith $ ExitFailure 1
-                         Ok x -> return x)
+  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)
 
+  allocnodes <- exitIfBad "failure during allocation" $
+                Cluster.genAllocNodes gl nl req_nodes True
 
-  let reqinst = iofspec ispec
+  -- Run the tiered allocation
 
-  allocnodes <- exitifbad $ Cluster.genAllocNodes gl nl req_nodes True
+  let tspec = fromMaybe (rspecFromISpec (iPolicyMaxSpec ipol))
+              (optTieredSpec opts)
 
-  -- Run the tiered allocation, if enabled
+  (treason, trl_nl, _, spec_map) <-
+    runAllocation cdata stop_allocation
+       (Cluster.tieredAlloc nl il alloclimit
+        (instFromSpec tspec disk_template su) allocnodes [] [])
+       tspec disk_template SpecTiered opts
 
-  (case optTieredSpec opts of
-     Nothing -> return ()
-     Just tspec -> do
-       (_, trl_nl, trl_il, trl_ixes, _) <-
-           if stop_allocation
-           then return result_noalloc
-           else exitifbad (Cluster.tieredAlloc nl il Nothing (iofspec tspec)
-                                  allocnodes [] [])
-       let spec_map' = Cluster.tieredSpecMap trl_ixes
-
-       printAllocationMap verbose "Tiered allocation map" trl_nl trl_ixes
-
-       maybePrintNodes shownodes "Tiered allocation"
-                           (Cluster.printNodes trl_nl)
-
-       maybeSaveData (optSaveCluster opts) "tiered" "after tiered allocation"
-                     (ClusterData gl trl_nl trl_il ctags)
-
-       printKeys $ map (\(a, fn) -> ("TSPEC_INI_" ++ a, fn tspec)) specData
-       printKeys $ printStats PTiered (Cluster.totalResources trl_nl)
-       printKeys [("TSPEC", intercalate " " spec_map')]
-       printAllocationStats m_cpu nl trl_nl)
+  printTiered machine_r spec_map 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 Nothing
-                      reqinst allocnodes [] [])
-
-  let allocs = length ixes
-      sreason = reverse $ sortBy (comparing snd) ereason
+  let ispec = fromMaybe (rspecFromISpec (iPolicyStdSpec ipol))
+              (optStdSpec opts)
 
-  printAllocationMap verbose "Standard allocation map" fin_nl ixes
+  (sreason, fin_nl, allocs, _) <-
+      runAllocation cdata stop_allocation
+            (Cluster.iterateAlloc nl il alloclimit
+             (instFromSpec ispec disk_template su) allocnodes [] [])
+            ispec disk_template SpecNormal opts
 
-  maybePrintNodes shownodes "Standard allocation" (Cluster.printNodes fin_nl)
+  printResults machine_r nl fin_nl num_instances allocs sreason
 
-  maybeSaveData (optSaveCluster opts) "alloc" "after standard allocation"
-       (ClusterData gl fin_nl fin_il ctags)
+  -- Print final result
 
-  printResults fin_nl num_instances allocs sreason
+  printFinalHTS machine_r