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