content: {-| IAllocator plugin for Ganeti.
  
  -}
  
  {-
  
  Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:
  
  1. Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
  
  2. Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
  
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  -}
  
  module Ganeti.HTools.Program.Hail
    ( main
    , options
    , arguments
    ) where
  
  import Control.Monad
  import Control.Monad.Writer (runWriterT)
  import Data.Maybe (fromMaybe, isJust)
  import System.IO
  
  import qualified Ganeti.HTools.AlgorithmParams as Alg
  import qualified Ganeti.HTools.Cluster as Cluster
  import qualified Ganeti.HTools.Dedicated as Dedicated
  
  import Ganeti.Common
  import Ganeti.HTools.CLI
  import Ganeti.HTools.Backend.IAlloc
  import qualified Ganeti.HTools.Backend.MonD as MonD
  import Ganeti.HTools.Loader (Request(..), ClusterData(..), isAllocationRequest)
  import Ganeti.HTools.ExtLoader (maybeSaveData, loadExternalData)
  import Ganeti.Utils
  
  -- | Options list and functions.
  options :: IO [OptType]
  options =
    return
      [ oPrintNodes
      , oSaveCluster
      , oDataFile
      , oNodeSim
      , oVerbose
      , oIgnoreDyn
      , oIgnoreSoftErrors
      , oNoCapacityChecks
      , oRestrictToNodes
      , oMonD
      , oMonDXen
      , oStaticKvmNodeMemory
      ]
  
  -- | The list of arguments supported by the program.
  arguments :: [ArgCompletion]
  arguments = [ArgCompletion OptComplFile 1 (Just 1)]
  
  wrapReadRequest :: Options -> [String] -> IO Request
  wrapReadRequest opts args = do
    let static_n_mem = optStaticKvmNodeMemory opts
    r1 <- case args of
            []    -> exitErr "This program needs an input file."
            _:_:_ -> exitErr "Only one argument is accepted (the input file)"
            x:_   -> readRequest x static_n_mem
  
    if isJust (optDataFile opts) ||  (not . null . optNodeSim) opts
      then do
        -- TODO: Cleanup this mess. ClusterData is loaded first in
        -- IAlloc.readRequest, then the data part is dropped and replaced with
        -- ExtLoader.loadExternalData that uses IAlloc.loadData to load the same
        -- data again. This codepath is executed only with a manually specified
        -- cluster data file or simulation (i.e. not under'normal' operation.)
        cdata <- loadExternalData opts
        let Request rqt _ = r1
        return $ Request rqt cdata
      else do
        let Request rqt cdata = r1
        (cdata', _) <- runWriterT $ if optMonD opts
                                      then MonD.queryAllMonDDCs cdata opts
                                      else return cdata
        return $ Request rqt cdata'
  
  -- | Main function.
  main :: Options -> [String] -> IO ()
  main opts args = do
    let shownodes = optShowNodes opts
        verbose = optVerbose opts
        savecluster = optSaveCluster opts
  
    request <- wrapReadRequest opts args
  
    let Request rq cdata = request
  
    when (verbose > 1) .
         hPutStrLn stderr $ "Received request: " ++ show rq
  
    when (verbose > 2) .
         hPutStrLn stderr $ "Received cluster data: " ++ show cdata
  
    let dedicatedAlloc = maybe False (Dedicated.isDedicated cdata)
                         $ isAllocationRequest rq
  
    when (verbose > 1 && dedicatedAlloc) $
        hPutStrLn stderr "Allocation on a dedicated cluster;\
                         \ using lost-allocations metrics."
  
    maybePrintNodes shownodes "Initial cluster"
         (Cluster.printNodes (cdNodes cdata))
  
    maybeSaveData savecluster "pre-ialloc" "before iallocator run" cdata
  
    let runAlloc = if dedicatedAlloc
                     then Dedicated.runDedicatedAllocation
                     else runIAllocator
        (maybe_ni, resp) = runAlloc (Alg.fromCLIOptions opts) request
        (fin_nl, fin_il) = fromMaybe (cdNodes cdata, cdInstances cdata) maybe_ni
    putStrLn resp
  
    maybePrintNodes shownodes "Final cluster" (Cluster.printNodes fin_nl)
  
    maybeSaveData savecluster "post-ialloc" "after iallocator run"
         (cdata { cdNodes = fin_nl, cdInstances = fin_il})
  