From b2278348535f54cd2548aad931cc14751db8fd1c Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Tue, 18 Aug 2009 18:07:54 +0200 Subject: [PATCH] Add a simulated cluster data loader This is useful especially for hspace, where we might want to simulate a hypothetical cluster to check allocation beforehand. --- Ganeti/HTools/CLI.hs | 12 +++++++ Ganeti/HTools/Simu.hs | 78 +++++++++++++++++++++++++++++++++++++++++++ Ganeti/HTools/Text.hs | 4 ++- hspace.1 | 30 +++++++++++++++++ hspace.hs | 1 + 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 Ganeti/HTools/Simu.hs diff --git a/Ganeti/HTools/CLI.hs b/Ganeti/HTools/CLI.hs index 78217da37..1672435e0 100644 --- a/Ganeti/HTools/CLI.hs +++ b/Ganeti/HTools/CLI.hs @@ -43,6 +43,7 @@ module Ganeti.HTools.CLI , oOutputDir , oNodeFile , oInstFile + , oNodeSim , oRapiMaster , oLuxiSocket , oMaxSolLength @@ -74,6 +75,7 @@ import Text.Printf (printf, hPrintf) import qualified Ganeti.HTools.Version as Version(version) import qualified Ganeti.HTools.Luxi as Luxi import qualified Ganeti.HTools.Rapi as Rapi +import qualified Ganeti.HTools.Simu as Simu import qualified Ganeti.HTools.Text as Text import qualified Ganeti.HTools.Loader as Loader import qualified Ganeti.HTools.Instance as Instance @@ -97,6 +99,7 @@ data Options = Options , optNodeSet :: Bool -- ^ The nodes have been set by options , optInstFile :: FilePath -- ^ Path to the instances file , optInstSet :: Bool -- ^ The insts have been set by options + , optNodeSim :: Maybe String -- ^ Cluster simulation mode , optMaxLength :: Int -- ^ Stop after this many steps , optMaster :: String -- ^ Collect data from RAPI , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi @@ -125,6 +128,7 @@ defaultOptions = Options , optNodeSet = False , optInstFile = "instances" , optInstSet = False + , optNodeSim = Nothing , optMaxLength = -1 , optMaster = "" , optLuxi = Nothing @@ -183,6 +187,11 @@ oInstFile = Option "i" ["instances"] (ReqArg (\ f o -> o { optInstFile = f, optInstSet = True }) "FILE") "the instance list FILE" +oNodeSim :: OptType +oNodeSim = Option "" ["simulate"] + (ReqArg (\ f o -> o { optNodeSim = Just f }) "SPEC") + "simulate an empty cluster, given as 'num_nodes,disk,memory,cpus'" + oRapiMaster :: OptType oRapiMaster = Option "m" ["master"] (ReqArg (\ m opts -> opts { optMaster = m }) "ADDRESS") @@ -330,8 +339,10 @@ loadExternalData opts = do else env_inst mhost = optMaster opts lsock = optLuxi opts + simdata = optNodeSim opts setRapi = mhost /= "" setLuxi = isJust lsock + setSim = isJust simdata setFiles = optNodeSet opts || optInstSet opts allSet = filter id [setRapi, setLuxi, setFiles] when (length allSet > 1) $ @@ -344,6 +355,7 @@ loadExternalData opts = do case () of _ | setRapi -> wrapIO $ Rapi.loadData mhost | setLuxi -> wrapIO $ Luxi.loadData $ fromJust lsock + | setSim -> Simu.loadData $ fromJust simdata | otherwise -> wrapIO $ Text.loadData nodef instf let ldresult = input_data >>= Loader.mergeData diff --git a/Ganeti/HTools/Simu.hs b/Ganeti/HTools/Simu.hs new file mode 100644 index 000000000..29bf6ff94 --- /dev/null +++ b/Ganeti/HTools/Simu.hs @@ -0,0 +1,78 @@ +{-| Parsing data from a simulated description of the cluster + +This module holds the code for parsing a cluster description. + +-} + +{- + +Copyright (C) 2009 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 +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +-} + +module Ganeti.HTools.Simu + ( + loadData + ) where + +import Control.Monad +import Text.Printf (printf) + +import Ganeti.HTools.Utils +import Ganeti.HTools.Types +import qualified Ganeti.HTools.Node as Node +import qualified Ganeti.HTools.Instance as Instance + +-- | Parse results from readsPrec +parseChoices :: (Monad m, Read a) => String -> String -> [(a, String)] -> m a +parseChoices _ _ ((v, ""):[]) = return v +parseChoices name s ((_, e):[]) = + fail $ name ++ ": leftover characters when parsing '" + ++ s ++ "': '" ++ e ++ "'" +parseChoices name s _ = fail $ name ++ ": cannot parse string '" ++ s ++ "'" + +-- | Safe 'read' function returning data encapsulated in a Result. +tryRead :: (Monad m, Read a) => String -> String -> m a +tryRead name s = parseChoices name s $ reads s + +-- | Parse the string description into nodes +parseDesc :: String -> Result (Int, Int, Int, Int) +parseDesc desc = + case sepSplit ',' desc of + n:d:m:c:[] -> do + ncount <- tryRead "node count" n + disk <- tryRead "disk size" d + mem <- tryRead "memory size" m + cpu <- tryRead "cpu count" c + return (ncount, disk, mem, cpu) + _ -> fail "Invalid cluster specification" + +-- | Builds the cluster data from node\/instance files. +loadData :: String -- ^ Cluster description in text format + -> IO (Result (Node.AssocList, Instance.AssocList)) +loadData ndata = -- IO monad, just for consistency with the other loaders + return $ do + (cnt, disk, mem, cpu) <- parseDesc ndata + let nodes = map (\idx -> + let n = Node.create (printf "node%03d" idx) + (fromIntegral mem) 0 mem + (fromIntegral disk) disk + (fromIntegral cpu) False + in (idx, Node.setIdx n idx) + ) [1..cnt] + return (nodes, []) diff --git a/Ganeti/HTools/Text.hs b/Ganeti/HTools/Text.hs index 5e67fac84..3db802e01 100644 --- a/Ganeti/HTools/Text.hs +++ b/Ganeti/HTools/Text.hs @@ -27,7 +27,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -} module Ganeti.HTools.Text - where + ( + loadData + ) where import Control.Monad diff --git a/hspace.1 b/hspace.1 index 960bc67ec..145f6194c 100644 --- a/hspace.1 +++ b/hspace.1 @@ -299,6 +299,36 @@ optional \fIpath\fR argument is interpreted as the path to the unix socket on which the master daemon listens; otherwise, the default path used by ganeti when installed with "--localstatedir=/var" is used. +.TP +.BI "--simulate " description +Instead of using actual data, build an empty cluster given a node +description. The \fIdescription\fR parameter must be a comma-separate +list of four elements, describing in order: + +.RS + +.RS +.TP +the number of nodes in the cluster + +.TP +the disk size of the nodes, in mebibytes + +.TP +the memory size of the nodes, in mebibytes + +.TP +the cpu core count for the nodes + +.RE + +An example description would be \fB20,102400,16384,4\fR describing a +20-node cluster where each node has 100GiB of disk space, 16GiB of +memory and 4 CPU cores. Note that all nodes must have the same specs +currently. + +.RE + .TP .B -v, --verbose Increase the output verbosity. Each usage of this option will increase diff --git a/hspace.hs b/hspace.hs index 41daebfff..c59c80d50 100644 --- a/hspace.hs +++ b/hspace.hs @@ -50,6 +50,7 @@ options = [ oPrintNodes , oNodeFile , oInstFile + , oNodeSim , oRapiMaster , oLuxiSocket , oVerbose -- GitLab