Revision 040afc35

b/Ganeti/HTools/Cluster.hs
85 85
data Table = Table NodeList InstanceList Score [Placement]
86 86
             deriving (Show)
87 87

  
88
-- | Constant node index for a non-moveable instance
89
noSecondary :: Int
90
noSecondary = -1
91

  
92 88
-- General functions
93 89

  
94 90
-- | Cap the removal list if needed.
......
480 476
        best_tbl =
481 477
            foldl'
482 478
            (\ step_tbl elem ->
483
                 if Instance.snode elem == noSecondary then step_tbl
479
                 if Instance.snode elem == Node.noSecondary then step_tbl
484 480
                    else compareTables step_tbl $
485 481
                         checkInstanceMove nodes_idx ini_tbl elem)
486 482
            ini_tbl victims
......
694 690

  
695 691
-- Loading functions
696 692

  
697
{- | Convert newline and delimiter-separated text.
698

  
699
This function converts a text in tabular format as generated by
700
@gnt-instance list@ and @gnt-node list@ to a list of objects using a
701
supplied conversion function.
702

  
703
-}
704
loadTabular :: (Monad m) => String -> ([String] -> m (String, a))
705
            -> (a -> Int -> a) -> m ([(String, Int)], [(Int, a)])
706
loadTabular text_data convert_fn set_fn = do
707
  let lines_data = lines text_data
708
      rows = map (sepSplit '|') lines_data
709
  kerows <- mapM convert_fn rows
710
  let idxrows = map (\ (idx, (k, v)) -> ((k, idx), (idx, set_fn v idx)))
711
                (zip [0..] kerows)
712
  return $ unzip idxrows
713

  
714 693
-- | For each instance, add its index to its primary and secondary nodes
715 694
fixNodes :: [(Int, Node.Node)]
716 695
         -> [(Int, Instance.Instance)]
......
726 705
                    ac1 = deleteBy assocEqual (pdx, pold) accu
727 706
                    ac2 = (pdx, pnew):ac1
728 707
                in
729
                  if sdx /= noSecondary then
708
                  if sdx /= Node.noSecondary then
730 709
                      let
731 710
                          sold = fromJust $ lookup sdx accu
732 711
                          snew = Node.setSec sold idx
......
756 735
    let sflen = length suffix in
757 736
    map (\ (key, name) -> (key, take ((length name) - sflen) name)) lst
758 737

  
759
-- | Safe 'read' function returning data encapsulated in a Result
760
tryRead :: (Monad m, Read a) => String -> String -> m a
761
tryRead name s =
762
    let sols = readsPrec 0 s
763
    in case sols of
764
         (v, ""):[] -> return v
765
         (_, e):[] -> fail $ name ++ ": leftover characters when parsing '"
766
                      ++ s ++ "': '" ++ e ++ "'"
767
         _ -> fail $ name ++ ": cannot parse string '" ++ s ++ "'"
768

  
769
-- | Lookups a node into an assoc list
770
lookupNode :: (Monad m) => String -> String -> [(String, Int)] -> m Int
771
lookupNode node inst ktn =
772
    case lookup node ktn of
773
      Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
774
      Just idx -> return idx
775

  
776
-- | Load a node from a field list
777
loadNode :: (Monad m) => [String] -> m (String, Node.Node)
778
loadNode (name:tm:nm:fm:td:fd:fo:[]) = do
779
  new_node <-
780
      if any (== "?") [tm,nm,fm,td,fd] || fo == "Y" then
781
          return $ Node.create 0 0 0 0 0 True
782
      else do
783
        vtm <- tryRead name tm
784
        vnm <- tryRead name nm
785
        vfm <- tryRead name fm
786
        vtd <- tryRead name td
787
        vfd <- tryRead name fd
788
        return $ Node.create vtm vnm vfm vtd vfd False
789
  return (name, new_node)
790
loadNode s = fail $ "Invalid/incomplete node data: '" ++ (show s) ++ "'"
791

  
792
-- | Load an instance from a field list
793
loadInst :: (Monad m) =>
794
            [(String, Int)] -> [String] -> m (String, Instance.Instance)
795
loadInst ktn (name:mem:dsk:status:pnode:snode:[]) = do
796
  pidx <- lookupNode pnode name ktn
797
  sidx <- (if null snode then return noSecondary
798
           else lookupNode snode name ktn)
799
  vmem <- tryRead name mem
800
  vdsk <- tryRead name dsk
801
  when (sidx == pidx) $ fail $ "Instance " ++ name ++
802
           " has same primary and secondary node - " ++ pnode
803
  let newinst = Instance.create vmem vdsk status pidx sidx
804
  return (name, newinst)
805
loadInst _ s = fail $ "Invalid/incomplete instance data: '" ++ (show s) ++ "'"
806 738

  
807 739
{-| Initializer function that loads the data from a node and list file
808 740
    and massages it into the correct format. -}
809
loadData :: String -- ^ Node data in text format
810
         -> String -- ^ Instance data in text format
811
         -> Result (Container.Container Node.Node,
812
                    Container.Container Instance.Instance,
813
                    String, NameList, NameList)
814
loadData ndata idata = do
815
  {- node file: name t_mem n_mem f_mem t_disk f_disk -}
816
  (ktn, nl) <- loadTabular ndata loadNode Node.setIdx
817
      {- instance file: name mem disk status pnode snode -}
818
  (kti, il) <- loadTabular idata (loadInst ktn) Instance.setIdx
741
loadData :: ([(String, Int)], Node.AssocList,
742
             [(String, Int)], Instance.AssocList) -- ^ Data from either
743
                                                  -- Text.loadData
744
                                                  -- or Rapi.loadData
745
         -> Result (NodeList, InstanceList, String, NameList, NameList)
746
loadData (ktn, nl, kti, il) = do
819 747
  let
820 748
      nl2 = fixNodes nl il
821 749
      il3 = Container.fromAssocList il
b/Ganeti/HTools/Instance.hs
17 17
                                            -- book-keeping
18 18
                         } deriving (Show)
19 19

  
20
-- | A simple name for the int, instance association list
21
type AssocList = [(Int, Instance)]
22

  
20 23
create :: Int -> Int -> String -> Int -> Int -> Instance
21 24
create mem_init dsk_init run_init pn sn =
22 25
    Instance {
b/Ganeti/HTools/Loader.hs
1
{-| Loading data from external sources
2

  
3
This module holds the common code for loading the cluster state from external sources.
4

  
5
-}
6

  
7
module Ganeti.HTools.Loader
8
    where
9

  
10
type NameAssoc = [(String, Int)]
11

  
12
-- | Lookups a node into an assoc list
13
lookupNode :: (Monad m) => NameAssoc -> String -> String -> m Int
14
lookupNode ktn inst node =
15
    case lookup node ktn of
16
      Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
17
      Just idx -> return idx
18

  
19
assignIndices :: (a -> Int -> a)
20
              -> [(String, a)]
21
              -> (NameAssoc, [(Int, a)])
22
assignIndices set_fn =
23
    unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, set_fn v idx)))
24
          . zip [0..]
b/Ganeti/HTools/Node.hs
26 26
    , setSec
27 27
    -- * Formatting
28 28
    , list
29
    -- * Misc stuff
30
    , AssocList
31
    , noSecondary
29 32
    ) where
30 33

  
31 34
import Data.List
......
58 61
                                   -- score computations
59 62
  } deriving (Show)
60 63

  
64
-- | A simple name for the int, node association list
65
type AssocList = [(Int, Node)]
66

  
67
-- | Constant node index for a non-moveable instance
68
noSecondary :: Int
69
noSecondary = -1
70

  
61 71
{- | Create a new node.
62 72

  
63 73
The index and the peers maps are empty, and will be need to be update
b/Ganeti/HTools/Rapi.hs
4 4

  
5 5
module Ganeti.HTools.Rapi
6 6
    (
7
      getNodes
8
    , getInstances
7
      loadData
9 8
    ) where
10 9

  
11 10
import Network.Curl
12 11
import Network.Curl.Types ()
13 12
import Network.Curl.Code
14
import Data.Either ()
15
import Data.Maybe
16 13
import Data.List
17 14
import Control.Monad
18 15
import Text.JSON (JSObject, JSValue)
19 16
import Text.Printf (printf)
17

  
20 18
import Ganeti.HTools.Utils
19
import Ganeti.HTools.Loader
20
import qualified Ganeti.HTools.Node as Node
21
import qualified Ganeti.HTools.Instance as Instance
21 22

  
22 23
-- | Read an URL via curl and return the body if successful
23 24
getUrl :: (Monad m) => String -> IO (m String)
......
35 36
    if elem ':' master then  master
36 37
    else "https://" ++ master ++ ":5080"
37 38

  
38
getInstances :: String -> IO (Result String)
39
getInstances master = do
40
  let url2 = printf "%s/2/instances?bulk=1" (formatHost master)
41
  body <- getUrl url2
42
  return $ (do x <- body
43
               arr <- loadJSArray x
44
               ilist <- mapM parseInstance arr
45
               return $ unlines ilist)
39
getInstances :: NameAssoc
40
             -> String
41
             -> Result [(String, Instance.Instance)]
42
getInstances ktn body = do
43
  arr <- loadJSArray body
44
  ilist <- mapM (parseInstance ktn) arr
45
  return ilist
46 46

  
47
getNodes :: String -> IO (Result String)
48
getNodes master = do
49
  let url2 = printf "%s/2/nodes?bulk=1" (formatHost master)
50
  body <- getUrl url2
51
  return $ (do x <- body
52
               arr <- loadJSArray x
53
               nlist <- mapM parseNode arr
54
               return $ unlines nlist)
47
getNodes :: String -> Result [(String, Node.Node)]
48
getNodes body = do
49
  arr <- loadJSArray body
50
  nlist <- mapM parseNode arr
51
  return nlist
55 52

  
56
parseInstance :: JSObject JSValue -> Result String
57
parseInstance a =
58
    let name = getStringElement "name" a
59
        disk = getIntElement "disk_usage" a
60
        mem = getObjectElement "beparams" a >>= getIntElement "memory"
61
        pnode = getStringElement "pnode" a
62
        snode = (liftM head $ getListElement "snodes" a) >>= readEitherString
63
        running = getStringElement "status" a
64
    in
65
      name |+ (show `liftM` mem) |+
66
              (show `liftM` disk) |+
67
              running |+ pnode |+ snode
53
parseInstance :: [(String, Int)]
54
              -> JSObject JSValue
55
              -> Result (String, Instance.Instance)
56
parseInstance ktn a = do
57
  name <- fromObj "name" a
58
  disk <- fromObj "disk_usage" a
59
  mem <- fromObj "beparams" a >>= fromObj "memory"
60
  pnode <- fromObj "pnode" a >>= lookupNode ktn name
61
  snodes <- getListElement "snodes" a
62
  snode <- (if null snodes then return Node.noSecondary
63
            else readEitherString (head snodes) >>= lookupNode ktn name)
64
  running <- fromObj "status" a
65
  let inst = Instance.create mem disk running pnode snode
66
  return (name, inst)
68 67

  
69
boolToYN :: (Monad m) => Bool -> m String
70
boolToYN True = return "Y"
71
boolToYN _ = return "N"
68
parseNode :: JSObject JSValue -> Result (String, Node.Node)
69
parseNode a = do
70
    name <- fromObj "name" a
71
    offline <- fromObj "offline" a
72
    node <- (case offline of
73
               True -> return $ Node.create 0 0 0 0 0 True
74
               _ -> do
75
                 drained <- fromObj "drained" a
76
                 mtotal <- fromObj "mtotal" a
77
                 mnode <- fromObj "mnode" a
78
                 mfree <- fromObj "mfree" a
79
                 dtotal <- fromObj "dtotal" a
80
                 dfree <- fromObj "dfree" a
81
                 return $ Node.create mtotal mnode mfree
82
                        dtotal dfree (offline || drained))
83
    return (name, node)
72 84

  
73
parseNode :: JSObject JSValue -> Result String
74
parseNode a =
75
    let name = getStringElement "name" a
76
        offline = getBoolElement "offline" a
77
        drained = getBoolElement "drained" a
78
        mtotal = getIntElement "mtotal" a
79
        mnode = getIntElement "mnode" a
80
        mfree = getIntElement "mfree" a
81
        dtotal = getIntElement "dtotal" a
82
        dfree = getIntElement "dfree" a
83
    in name |+
84
       (case offline of
85
          Ok True -> Ok "0|0|0|0|0|Y"
86
          _ ->
87
              (show `liftM` mtotal) |+ (show `liftM` mnode) |+
88
              (show `liftM` mfree) |+ (show `liftM` dtotal) |+
89
              (show `liftM` dfree) |+
90
              ((liftM2 (||) offline drained) >>= boolToYN)
91
       )
85
loadData :: String -- ^ Cluster/URL to use as source
86
         -> IO (Result (NameAssoc, Node.AssocList,
87
                        NameAssoc, Instance.AssocList))
88
loadData master = do -- IO monad
89
  let url = formatHost master
90
  node_body <- getUrl $ printf "%s/2/nodes?bulk=1" url
91
  inst_body <- getUrl $ printf "%s/2/instances?bulk=1" url
92
  return $ do -- Result monad
93
    node_data <- node_body >>= getNodes
94
    let (node_names, node_idx) = assignIndices Node.setIdx node_data
95
    inst_data <- inst_body >>= getInstances node_names
96
    let (inst_names, inst_idx) = assignIndices Instance.setIdx inst_data
97
    return (node_names, node_idx, inst_names, inst_idx)
b/Ganeti/HTools/Text.hs
1
{-| Parsing data from text-files
2

  
3
This module holds the code for loading the cluster state from text
4
files, as produced by gnt-node/gnt-instance list.
5

  
6
-}
7

  
8
module Ganeti.HTools.Text
9
    where
10

  
11
import Control.Monad
12

  
13
import Ganeti.HTools.Utils
14
import Ganeti.HTools.Loader
15
import qualified Ganeti.HTools.Node as Node
16
import qualified Ganeti.HTools.Instance as Instance
17

  
18
-- | Safe 'read' function returning data encapsulated in a Result
19
tryRead :: (Monad m, Read a) => String -> String -> m a
20
tryRead name s =
21
    let sols = readsPrec 0 s
22
    in case sols of
23
         (v, ""):[] -> return v
24
         (_, e):[] -> fail $ name ++ ": leftover characters when parsing '"
25
                      ++ s ++ "': '" ++ e ++ "'"
26
         _ -> fail $ name ++ ": cannot parse string '" ++ s ++ "'"
27

  
28
-- | Load a node from a field list
29
loadNode :: (Monad m) => [String] -> m (String, Node.Node)
30
loadNode (name:tm:nm:fm:td:fd:fo:[]) = do
31
  new_node <-
32
      if any (== "?") [tm,nm,fm,td,fd] || fo == "Y" then
33
          return $ Node.create 0 0 0 0 0 True
34
      else do
35
        vtm <- tryRead name tm
36
        vnm <- tryRead name nm
37
        vfm <- tryRead name fm
38
        vtd <- tryRead name td
39
        vfd <- tryRead name fd
40
        return $ Node.create vtm vnm vfm vtd vfd False
41
  return (name, new_node)
42
loadNode s = fail $ "Invalid/incomplete node data: '" ++ (show s) ++ "'"
43

  
44
-- | Load an instance from a field list
45
loadInst :: (Monad m) =>
46
            [(String, Int)] -> [String] -> m (String, Instance.Instance)
47
loadInst ktn (name:mem:dsk:status:pnode:snode:[]) = do
48
  pidx <- lookupNode ktn name pnode
49
  sidx <- (if null snode then return Node.noSecondary
50
           else lookupNode ktn name snode)
51
  vmem <- tryRead name mem
52
  vdsk <- tryRead name dsk
53
  when (sidx == pidx) $ fail $ "Instance " ++ name ++
54
           " has same primary and secondary node - " ++ pnode
55
  let newinst = Instance.create vmem vdsk status pidx sidx
56
  return (name, newinst)
57
loadInst _ s = fail $ "Invalid/incomplete instance data: '" ++ (show s) ++ "'"
58

  
59
{- | Convert newline and delimiter-separated text.
60

  
61
This function converts a text in tabular format as generated by
62
@gnt-instance list@ and @gnt-node list@ to a list of objects using a
63
supplied conversion function.
64

  
65
-}
66
loadTabular :: (Monad m) => String -> ([String] -> m (String, a))
67
            -> (a -> Int -> a) -> m ([(String, Int)], [(Int, a)])
68
loadTabular text_data convert_fn set_fn = do
69
  let lines_data = lines text_data
70
      rows = map (sepSplit '|') lines_data
71
  kerows <- mapM convert_fn rows
72
  return $ assignIndices set_fn kerows
73

  
74
loadData :: String -- ^ Node data in string format
75
         -> String -- ^ Instance data in string format
76
         -> IO (Result (NameAssoc, Node.AssocList,
77
                        NameAssoc, Instance.AssocList))
78
loadData nfile ifile = do -- IO monad
79
  ndata <- readFile nfile
80
  idata <- readFile ifile
81
  return $ do
82
    {- node file: name t_mem n_mem f_mem t_disk f_disk -}
83
    (ktn, nl) <- loadTabular ndata loadNode Node.setIdx
84
    {- instance file: name mem disk status pnode snode -}
85
    (kti, il) <- loadTabular idata (loadInst ktn) Instance.setIdx
86
    return (ktn, nl, kti, il)
b/hbal.hs
19 19
import qualified Ganeti.HTools.Cluster as Cluster
20 20
import qualified Ganeti.HTools.Node as Node
21 21
import qualified Ganeti.HTools.CLI as CLI
22
import Ganeti.HTools.Rapi
22
import qualified Ganeti.HTools.Rapi as Rapi
23
import qualified Ganeti.HTools.Text as Text
24

  
23 25
import Ganeti.HTools.Utils
24 26

  
25 27
-- | Command line options structure.
......
179 181
              else env_inst
180 182
      oneline = optOneline opts
181 183
      verbose = optVerbose opts
182
      (node_data, inst_data) =
183
          case optMaster opts of
184
            "" -> (readFile nodef,
185
                   readFile instf)
186
            host -> (getNodes host >>= readData,
187
                     getInstances host >>= readData)
188

  
189
  ldresult <- liftM2 Cluster.loadData node_data inst_data
184
  input_data <-
185
      case optMaster opts of
186
        "" -> Text.loadData nodef instf
187
        host -> Rapi.loadData host
188

  
189
  let ldresult = input_data >> Cluster.loadData
190

  
190 191
  (loaded_nl, il, csf, ktn, kti) <-
191 192
      (case ldresult of
192 193
         Ok x -> return x
b/hn1.hs
18 18
import qualified Ganeti.HTools.Instance as Instance
19 19
import qualified Ganeti.HTools.Cluster as Cluster
20 20
import qualified Ganeti.HTools.CLI as CLI
21
import Ganeti.HTools.Rapi
21
import qualified Ganeti.HTools.Rapi as Rapi
22
import qualified Ganeti.HTools.Text as Text
22 23
import Ganeti.HTools.Utils
23 24

  
24 25
-- | Command line options structure.
......
144 145
      instf = if optInstSet opts then optInstf opts
145 146
              else env_inst
146 147
      min_depth = optMinDepth opts
147
      (node_data, inst_data) =
148
          case optMaster opts of
149
            "" -> (readFile nodef,
150
                   readFile instf)
151
            host -> (getNodes host >>= readData,
152
                     getInstances host >>= readData)
153

  
154
  ldresult <- liftM2 Cluster.loadData node_data inst_data
148

  
149
  input_data <-
150
      case optMaster opts of
151
        "" -> Text.loadData nodef instf
152
        host -> Rapi.loadData host
153
  let ldresult = input_data >>= Cluster.loadData
154

  
155 155
  (loaded_nl, il, csf, ktn, kti) <-
156 156
      (case ldresult of
157 157
         Ok x -> return x
b/hscan.hs
21 21
import qualified Ganeti.HTools.Node as Node
22 22
import qualified Ganeti.HTools.Instance as Instance
23 23
import qualified Ganeti.HTools.CLI as CLI
24
import Ganeti.HTools.Rapi
24
import qualified Ganeti.HTools.Rapi as Rapi
25 25
import Ganeti.HTools.Utils
26 26

  
27 27
-- | Command line options structure.
......
158 158
            do
159 159
              printf "%-*s " nlen name
160 160
              hFlush stdout
161
              node_data <- getNodes name
162
              inst_data <- getInstances name
163
              let ldresult = join $
164
                             liftM2 Cluster.loadData node_data inst_data
161
              input_data <- Rapi.loadData name
162
              let ldresult = input_data >>= Cluster.loadData
165 163
              (case ldresult of
166 164
                 Bad err -> printf "\nError: failed to load data. \
167 165
                                   \Details:\n%s\n" err

Also available in: Unified diff