Commit 99b63608 authored by Iustin Pop's avatar Iustin Pop
Browse files

Rework the types used during data loading



This improves on the previous change. Currently, the node and instance
lists shipped around during data loading are (again) association lists.
For instances it's not a big issue, but the node list is rewritten
continuously while we assign instances to nodes, and that is very slow.
The code was originally written for small (10-20 node) clusters, and
today with multinodes… :)

Rewriting to use Node.List/Instance.List makes a bit of a messy patch,
but it allows to remove some custom functions for assoc. list
processing, and also some custom unittests.

At the end, the load time is almost halved, and we spend time now just
in the string parsing code (which is, as we know, slow…).
Signed-off-by: default avatarIustin Pop <iustin@google.com>
Reviewed-by: default avatarBalazs Lecz <leczb@google.com>
parent 2d0ca2c5
......@@ -37,9 +37,7 @@ module Ganeti.HTools.Loader
, Request(..)
) where
import Data.Function (on)
import Data.List
import Data.Maybe (fromJust)
import qualified Data.Map as M
import Text.Printf (printf)
......@@ -93,35 +91,29 @@ lookupInstance kti inst =
-- | Given a list of elements (and their names), assign indices to them.
assignIndices :: (Element a) =>
[(String, a)]
-> (NameAssoc, [(Int, a)])
-> (NameAssoc, Container.Container a)
assignIndices nodes =
let (na, idx_node) =
unzip . map (\ (idx, (k, v)) -> ((k, idx), (idx, setIdx v idx)))
. zip [0..] $ nodes
in (M.fromList na, idx_node)
-- | Assoc element comparator
assocEqual :: (Eq a) => (a, b) -> (a, b) -> Bool
assocEqual = (==) `on` fst
in (M.fromList na, Container.fromAssocList idx_node)
-- | For each instance, add its index to its primary and secondary nodes.
fixNodes :: [(Ndx, Node.Node)]
fixNodes :: Node.List
-> Instance.Instance
-> [(Ndx, Node.Node)]
-> Node.List
fixNodes accu inst =
let
pdx = Instance.pNode inst
sdx = Instance.sNode inst
pold = fromJust $ lookup pdx accu
pold = Container.find pdx accu
pnew = Node.setPri pold inst
ac1 = deleteBy assocEqual (pdx, pold) accu
ac2 = (pdx, pnew):ac1
ac2 = Container.add pdx pnew accu
in
if sdx /= Node.noSecondary
then let sold = fromJust $ lookup sdx accu
then let sold = Container.find sdx accu
snew = Node.setSec sold inst
ac3 = deleteBy assocEqual (sdx, sold) ac2
in (sdx, snew):ac3
in Container.add sdx snew ac2
else ac2
-- | Remove non-selected tags from the exclusion list
......@@ -168,11 +160,11 @@ commonSuffix nl il =
mergeData :: [(String, DynUtil)] -- ^ Instance utilisation data
-> [String] -- ^ Exclusion tags
-> [String] -- ^ Untouchable instances
-> (Node.AssocList, Instance.AssocList, [String])
-> (Node.List, Instance.List, [String])
-- ^ Data from backends
-> Result (Node.List, Instance.List, [String])
mergeData um extags exinsts (nl, il, tags) =
let il2 = Container.fromAssocList il
mergeData um extags exinsts (nl, il2, tags) =
let il = Container.elems il2
il3 = foldl' (\im (name, n_util) ->
case Container.findByName im name of
Nothing -> im -- skipping unknown instance
......@@ -184,10 +176,9 @@ mergeData um extags exinsts (nl, il, tags) =
il4 = Container.map (filterExTags allextags .
updateMovable exinsts) il3
nl2 = foldl' fixNodes nl (Container.elems il4)
nl3 = Container.fromAssocList
(map (\ (k, v) -> (k, Node.buildPeers v il4)) nl2)
node_names = map (Node.name . snd) nl
inst_names = map (Instance.name . snd) il
nl3 = Container.map (\node -> Node.buildPeers node il4) nl2
node_names = map Node.name (Container.elems nl)
inst_names = map Instance.name il
common_suffix = longestDomain (node_names ++ inst_names)
snl = Container.map (computeAlias common_suffix) nl3
sil = Container.map (computeAlias common_suffix) il4
......
......@@ -162,7 +162,7 @@ readData master =
)
parseData :: (Result JSValue, Result JSValue, Result JSValue)
-> Result (Node.AssocList, Instance.AssocList, [String])
-> Result (Node.List, Instance.List, [String])
parseData (nodes, instances, cinfo) = do
node_data <- nodes >>= getNodes
let (node_names, node_idx) = assignIndices node_data
......@@ -173,5 +173,5 @@ parseData (nodes, instances, cinfo) = do
-- | Top level function for data loading
loadData :: String -- ^ Unix socket to use as source
-> IO (Result (Node.AssocList, Instance.AssocList, [String]))
-> IO (Result (Node.List, Instance.List, [String]))
loadData master = readData master >>= return . parseData
......@@ -110,7 +110,7 @@ makeSmallCluster node count =
let fn = Node.buildPeers node Container.empty
namelst = map (\n -> (Node.name n, n)) (replicate count fn)
(_, nlst) = Loader.assignIndices namelst
in Container.fromAssocList nlst
in nlst
-- | Checks if a node is "big" enough
isNodeBig :: Node.Node -> Int -> Bool
......@@ -448,11 +448,12 @@ prop_Text_Load_Instance name mem dsk vcpus status pnode snode pdx sdx =
ndx = if null snode
then [(pnode, pdx)]
else [(pnode, pdx), (snode, rsdx)]
nl = Data.Map.fromList ndx
tags = ""
inst = Text.loadInst ndx
inst = Text.loadInst nl
[name, mem_s, dsk_s, vcpus_s, status, pnode, snode, tags]::
Maybe (String, Instance.Instance)
fail1 = Text.loadInst ndx
fail1 = Text.loadInst nl
[name, mem_s, dsk_s, vcpus_s, status, pnode, pnode, tags]::
Maybe (String, Instance.Instance)
_types = ( name::String, mem::Int, dsk::Int
......@@ -473,7 +474,8 @@ prop_Text_Load_Instance name mem dsk vcpus status pnode snode pdx sdx =
isNothing fail1)
prop_Text_Load_InstanceFail ktn fields =
length fields /= 8 ==> isNothing $ Text.loadInst ktn fields
length fields /= 8 ==> isNothing $ Text.loadInst nl fields
where nl = Data.Map.fromList ktn
prop_Text_Load_Node name tm nm fm td fd tc fo =
let conv v = if v < 0
......@@ -822,35 +824,27 @@ testJobs =
-- | Loader tests
prop_Loader_lookupNode ktn inst node =
isJust (Loader.lookupNode ktn inst node) == (node `elem` names)
where names = map fst ktn
Loader.lookupNode nl inst node == Data.Map.lookup node nl
where nl = Data.Map.fromList ktn
prop_Loader_lookupInstance kti inst =
isJust (Loader.lookupInstance kti inst) == (inst `elem` names)
where names = map fst kti
prop_Loader_lookupInstanceIdx kti inst =
case (Loader.lookupInstance kti inst,
findIndex (\p -> fst p == inst) kti) of
(Nothing, Nothing) -> True
(Just idx, Just ex) -> idx == snd (kti !! ex)
_ -> False
prop_Loader_assignIndices enames =
length nassoc == length enames &&
length kt == length enames &&
(if not (null enames)
then maximum (map fst kt) == length enames - 1
Loader.lookupInstance il inst == Data.Map.lookup inst il
where il = Data.Map.fromList kti
prop_Loader_assignIndices nodes =
Data.Map.size nassoc == length nodes &&
Container.size kt == length nodes &&
(if not (null nodes)
then maximum (IntMap.keys kt) == length nodes - 1
else True)
where (nassoc, kt) = Loader.assignIndices enames
_types = enames::[(String, Node.Node)]
where (nassoc, kt) = Loader.assignIndices (map (\n -> (Node.name n, n)) nodes)
-- | Checks that the number of primary instances recorded on the nodes
-- is zero
prop_Loader_mergeData ns =
let na = map (\n -> (Node.idx n, n)) ns
in case Loader.mergeData [] [] [] (na, [], []) of
let na = Container.fromAssocList $ map (\n -> (Node.idx n, n)) ns
in case Loader.mergeData [] [] [] (na, Container.empty, []) of
Types.Bad _ -> False
Types.Ok (nl, il, _) ->
let nodes = Container.elems nl
......@@ -861,7 +855,6 @@ prop_Loader_mergeData ns =
testLoader =
[ run prop_Loader_lookupNode
, run prop_Loader_lookupInstance
, run prop_Loader_lookupInstanceIdx
, run prop_Loader_assignIndices
, run prop_Loader_mergeData
]
......@@ -131,7 +131,7 @@ readData master = do
-- | Builds the cluster data from the raw Rapi content
parseData :: (Result String, Result String, Result String)
-> Result (Node.AssocList, Instance.AssocList, [String])
-> Result (Node.List, Instance.List, [String])
parseData (node_body, inst_body, tags_body) = do
node_data <- node_body >>= getNodes
let (node_names, node_idx) = assignIndices node_data
......@@ -142,5 +142,5 @@ parseData (node_body, inst_body, tags_body) = do
-- | Top level function for data loading
loadData :: String -- ^ Cluster or URL to use as source
-> IO (Result (Node.AssocList, Instance.AssocList, [String]))
-> IO (Result (Node.List, Instance.List, [String]))
loadData master = readData master >>= return . parseData
......@@ -6,7 +6,7 @@ This module holds the code for parsing a cluster description.
{-
Copyright (C) 2009 Google Inc.
Copyright (C) 2009, 2010 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
......@@ -35,6 +35,7 @@ import Text.Printf (printf)
import Ganeti.HTools.Utils
import Ganeti.HTools.Types
import qualified Ganeti.HTools.Container as Container
import qualified Ganeti.HTools.Node as Node
import qualified Ganeti.HTools.Instance as Instance
......@@ -52,7 +53,7 @@ parseDesc desc =
-- | Builds the cluster data from node\/instance files.
parseData :: String -- ^ Cluster description in text format
-> Result (Node.AssocList, Instance.AssocList, [String])
-> Result (Node.List, Instance.List, [String])
parseData ndata = do
(cnt, disk, mem, cpu) <- parseDesc ndata
let nodes = map (\idx ->
......@@ -62,10 +63,10 @@ parseData ndata = do
(fromIntegral cpu) False defaultGroupID
in (idx, Node.setIdx n idx)
) [1..cnt]
return (nodes, [], [])
return (Container.fromAssocList nodes, Container.empty, [])
-- | Builds the cluster data from node\/instance files.
loadData :: String -- ^ Cluster description in text format
-> IO (Result (Node.AssocList, Instance.AssocList, [String]))
-> IO (Result (Node.List, Instance.List, [String]))
loadData = -- IO monad, just for consistency with the other loaders
return . parseData
......@@ -135,7 +135,7 @@ loadInst _ s = fail $ "Invalid/incomplete instance data: '" ++ show s ++ "'"
-- a supplied conversion function.
loadTabular :: (Monad m, Element a) =>
[String] -> ([String] -> m (String, a))
-> m (NameAssoc, [(Int, a)])
-> m (NameAssoc, Container.Container a)
loadTabular lines_data convert_fn = do
let rows = map (sepSplit '|') lines_data
kerows <- mapM convert_fn rows
......@@ -148,7 +148,7 @@ readData = readFile
-- | Builds the cluster data from text input.
parseData :: String -- ^ Text data
-> Result (Node.AssocList, Instance.AssocList, [String])
-> Result (Node.List, Instance.List, [String])
parseData fdata = do
let flines = lines fdata
(nlines, ilines) = break null flines
......@@ -163,5 +163,5 @@ parseData fdata = do
-- | Top level function for data loading
loadData :: String -- ^ Path to the text file
-> IO (Result (Node.AssocList, Instance.AssocList, [String]))
-> IO (Result (Node.List, Instance.List, [String]))
loadData afile = readData afile >>= return . parseData
......@@ -89,7 +89,7 @@ fixSlash = map (\x -> if x == '/' then '_' else x)
-- | Generates serialized data from loader input
processData :: Result (Node.AssocList, Instance.AssocList, [String])
processData :: Result (Node.List, Instance.List, [String])
-> Result (Node.List, Instance.List, String)
processData input_data = do
(nl, il, _) <- input_data >>= Loader.mergeData [] [] []
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment