Revision ebf38064
b/htools/Ganeti/HTools/Compat.hs | ||
---|---|---|
2 | 2 |
|
3 | 3 |
{- | Compatibility helper module. |
4 | 4 |
|
5 |
This module holds definitions that help with supporting multiple library versions or transitions between versions. |
|
5 |
This module holds definitions that help with supporting multiple |
|
6 |
library versions or transitions between versions. |
|
6 | 7 |
|
7 | 8 |
-} |
8 | 9 |
|
... | ... | |
28 | 29 |
-} |
29 | 30 |
|
30 | 31 |
module Ganeti.HTools.Compat |
31 |
( rwhnf
|
|
32 |
, Control.Parallel.Strategies.parMap
|
|
33 |
) where
|
|
32 |
( rwhnf |
|
33 |
, Control.Parallel.Strategies.parMap |
|
34 |
) where |
|
34 | 35 |
|
35 | 36 |
import qualified Control.Parallel.Strategies |
36 | 37 |
|
b/htools/Ganeti/HTools/Container.hs | ||
---|---|---|
27 | 27 |
-} |
28 | 28 |
|
29 | 29 |
module Ganeti.HTools.Container |
30 |
( |
|
31 |
-- * Types |
|
32 |
Container |
|
33 |
, Key |
|
34 |
-- * Creation |
|
35 |
, IntMap.empty |
|
36 |
, IntMap.singleton |
|
37 |
, IntMap.fromList |
|
38 |
-- * Query |
|
39 |
, IntMap.size |
|
40 |
, IntMap.null |
|
41 |
, find |
|
42 |
, IntMap.findMax |
|
43 |
, IntMap.member |
|
44 |
-- * Update |
|
45 |
, add |
|
46 |
, addTwo |
|
47 |
, IntMap.map |
|
48 |
, IntMap.mapAccum |
|
49 |
, IntMap.filter |
|
50 |
-- * Conversion |
|
51 |
, IntMap.elems |
|
52 |
, IntMap.keys |
|
53 |
-- * Element functions |
|
54 |
, nameOf |
|
55 |
, findByName |
|
56 |
) where |
|
30 |
( -- * Types |
|
31 |
Container |
|
32 |
, Key |
|
33 |
-- * Creation |
|
34 |
, IntMap.empty |
|
35 |
, IntMap.singleton |
|
36 |
, IntMap.fromList |
|
37 |
-- * Query |
|
38 |
, IntMap.size |
|
39 |
, IntMap.null |
|
40 |
, find |
|
41 |
, IntMap.findMax |
|
42 |
, IntMap.member |
|
43 |
-- * Update |
|
44 |
, add |
|
45 |
, addTwo |
|
46 |
, IntMap.map |
|
47 |
, IntMap.mapAccum |
|
48 |
, IntMap.filter |
|
49 |
-- * Conversion |
|
50 |
, IntMap.elems |
|
51 |
, IntMap.keys |
|
52 |
-- * Element functions |
|
53 |
, nameOf |
|
54 |
, findByName |
|
55 |
) where |
|
57 | 56 |
|
58 | 57 |
import qualified Data.IntMap as IntMap |
59 | 58 |
|
... | ... | |
86 | 85 |
findByName :: (T.Element a, Monad m) => |
87 | 86 |
Container a -> String -> m a |
88 | 87 |
findByName c n = |
89 |
let all_elems = IntMap.elems c |
|
90 |
result = filter ((n `elem`) . T.allNames) all_elems |
|
91 |
in case result of |
|
92 |
[item] -> return item |
|
93 |
_ -> fail $ "Wrong number of elems found with name " ++ n |
|
88 |
let all_elems = IntMap.elems c |
|
89 |
result = filter ((n `elem`) . T.allNames) all_elems |
|
90 |
in case result of |
|
91 |
[item] -> return item |
|
92 |
_ -> fail $ "Wrong number of elems found with name " ++ n |
b/htools/Ganeti/HTools/ExtLoader.hs | ||
---|---|---|
28 | 28 |
-} |
29 | 29 |
|
30 | 30 |
module Ganeti.HTools.ExtLoader |
31 |
( loadExternalData
|
|
32 |
, commonSuffix
|
|
33 |
, maybeSaveData
|
|
34 |
) where
|
|
31 |
( loadExternalData |
|
32 |
, commonSuffix |
|
33 |
, maybeSaveData |
|
34 |
) where |
|
35 | 35 |
|
36 | 36 |
import Control.Monad |
37 | 37 |
import Data.Maybe (isJust, fromJust) |
... | ... | |
58 | 58 |
-- | Parses a user-supplied utilisation string. |
59 | 59 |
parseUtilisation :: String -> Result (String, DynUtil) |
60 | 60 |
parseUtilisation line = |
61 |
case sepSplit ' ' line of
|
|
62 |
[name, cpu, mem, dsk, net] ->
|
|
63 |
do
|
|
64 |
rcpu <- tryRead name cpu
|
|
65 |
rmem <- tryRead name mem
|
|
66 |
rdsk <- tryRead name dsk
|
|
67 |
rnet <- tryRead name net
|
|
68 |
let du = DynUtil { cpuWeight = rcpu, memWeight = rmem
|
|
69 |
, dskWeight = rdsk, netWeight = rnet }
|
|
70 |
return (name, du)
|
|
71 |
_ -> Bad $ "Cannot parse line " ++ line
|
|
61 |
case sepSplit ' ' line of |
|
62 |
[name, cpu, mem, dsk, net] -> |
|
63 |
do |
|
64 |
rcpu <- tryRead name cpu |
|
65 |
rmem <- tryRead name mem |
|
66 |
rdsk <- tryRead name dsk |
|
67 |
rnet <- tryRead name net |
|
68 |
let du = DynUtil { cpuWeight = rcpu, memWeight = rmem |
|
69 |
, dskWeight = rdsk, netWeight = rnet } |
|
70 |
return (name, du) |
|
71 |
_ -> Bad $ "Cannot parse line " ++ line |
|
72 | 72 |
|
73 | 73 |
-- | External tool data loader from a variety of sources. |
74 | 74 |
loadExternalData :: Options |
... | ... | |
100 | 100 |
Nothing -> return "") |
101 | 101 |
let util_data = mapM parseUtilisation $ lines util_contents |
102 | 102 |
util_data' <- (case util_data of |
103 |
Ok x -> return x |
|
103 |
Ok x -> return x
|
|
104 | 104 |
Bad y -> do |
105 | 105 |
hPutStrLn stderr ("Error: can't parse utilisation" ++ |
106 | 106 |
" data: " ++ show y) |
107 | 107 |
exitWith $ ExitFailure 1) |
108 | 108 |
input_data <- |
109 |
case () of
|
|
110 |
_ | setRapi -> wrapIO $ Rapi.loadData mhost
|
|
111 |
| setLuxi -> wrapIO $ Luxi.loadData $ fromJust lsock
|
|
112 |
| setSim -> Simu.loadData simdata
|
|
113 |
| setFile -> wrapIO $ Text.loadData $ fromJust tfile
|
|
114 |
| otherwise -> return $ Bad "No backend selected! Exiting."
|
|
109 |
case () of |
|
110 |
_ | setRapi -> wrapIO $ Rapi.loadData mhost |
|
111 |
| setLuxi -> wrapIO $ Luxi.loadData $ fromJust lsock |
|
112 |
| setSim -> Simu.loadData simdata |
|
113 |
| setFile -> wrapIO $ Text.loadData $ fromJust tfile |
|
114 |
| otherwise -> return $ Bad "No backend selected! Exiting." |
|
115 | 115 |
|
116 | 116 |
let ldresult = input_data >>= mergeData util_data' exTags selInsts exInsts |
117 | 117 |
cdata <- |
118 |
(case ldresult of
|
|
119 |
Ok x -> return x
|
|
120 |
Bad s -> do
|
|
121 |
hPrintf stderr
|
|
122 |
"Error: failed to load data, aborting. Details:\n%s\n" s:: IO ()
|
|
123 |
exitWith $ ExitFailure 1
|
|
124 |
)
|
|
118 |
(case ldresult of |
|
119 |
Ok x -> return x |
|
120 |
Bad s -> do |
|
121 |
hPrintf stderr |
|
122 |
"Error: failed to load data, aborting. Details:\n%s\n" s:: IO () |
|
123 |
exitWith $ ExitFailure 1 |
|
124 |
) |
|
125 | 125 |
let (fix_msgs, nl) = checkData (cdNodes cdata) (cdInstances cdata) |
126 | 126 |
|
127 | 127 |
unless (optVerbose opts == 0) $ maybeShowWarnings fix_msgs |
b/htools/Ganeti/HTools/Group.hs | ||
---|---|---|
24 | 24 |
-} |
25 | 25 |
|
26 | 26 |
module Ganeti.HTools.Group |
27 |
( Group(..)
|
|
28 |
, List
|
|
29 |
, AssocList
|
|
30 |
-- * Constructor
|
|
31 |
, create
|
|
32 |
, setIdx
|
|
33 |
, isAllocable
|
|
34 |
) where
|
|
27 |
( Group(..) |
|
28 |
, List |
|
29 |
, AssocList |
|
30 |
-- * Constructor |
|
31 |
, create |
|
32 |
, setIdx |
|
33 |
, isAllocable |
|
34 |
) where |
|
35 | 35 |
|
36 | 36 |
import qualified Ganeti.HTools.Container as Container |
37 | 37 |
|
... | ... | |
41 | 41 |
|
42 | 42 |
-- | The node group type. |
43 | 43 |
data Group = Group |
44 |
{ name :: String -- ^ The node name
|
|
45 |
, uuid :: T.GroupID -- ^ The UUID of the group
|
|
46 |
, idx :: T.Gdx -- ^ Internal index for book-keeping
|
|
47 |
, allocPolicy :: T.AllocPolicy -- ^ The allocation policy for this group
|
|
48 |
} deriving (Show, Read, Eq)
|
|
44 |
{ name :: String -- ^ The node name |
|
45 |
, uuid :: T.GroupID -- ^ The UUID of the group |
|
46 |
, idx :: T.Gdx -- ^ Internal index for book-keeping |
|
47 |
, allocPolicy :: T.AllocPolicy -- ^ The allocation policy for this group |
|
48 |
} deriving (Show, Read, Eq) |
|
49 | 49 |
|
50 | 50 |
-- Note: we use the name as the alias, and the UUID as the official |
51 | 51 |
-- name |
52 | 52 |
instance T.Element Group where |
53 |
nameOf = uuid
|
|
54 |
idxOf = idx
|
|
55 |
setAlias = setName
|
|
56 |
setIdx = setIdx
|
|
57 |
allNames n = [name n, uuid n]
|
|
53 |
nameOf = uuid |
|
54 |
idxOf = idx |
|
55 |
setAlias = setName |
|
56 |
setIdx = setIdx |
|
57 |
allNames n = [name n, uuid n] |
|
58 | 58 |
|
59 | 59 |
-- | A simple name for the int, node association list. |
60 | 60 |
type AssocList = [(T.Gdx, Group)] |
... | ... | |
67 | 67 |
-- | Create a new group. |
68 | 68 |
create :: String -> T.GroupID -> T.AllocPolicy -> Group |
69 | 69 |
create name_init id_init apol_init = |
70 |
Group { name = name_init
|
|
71 |
, uuid = id_init
|
|
72 |
, allocPolicy = apol_init
|
|
73 |
, idx = -1
|
|
74 |
}
|
|
70 |
Group { name = name_init |
|
71 |
, uuid = id_init |
|
72 |
, allocPolicy = apol_init |
|
73 |
, idx = -1 |
|
74 |
} |
|
75 | 75 |
|
76 | 76 |
-- | Sets the group index. |
77 | 77 |
-- |
b/htools/Ganeti/HTools/Instance.hs | ||
---|---|---|
27 | 27 |
-} |
28 | 28 |
|
29 | 29 |
module Ganeti.HTools.Instance |
30 |
( Instance(..)
|
|
31 |
, AssocList
|
|
32 |
, List
|
|
33 |
, create
|
|
34 |
, instanceRunning
|
|
35 |
, instanceOffline
|
|
36 |
, instanceDown
|
|
37 |
, applyIfOnline
|
|
38 |
, setIdx
|
|
39 |
, setName
|
|
40 |
, setAlias
|
|
41 |
, setPri
|
|
42 |
, setSec
|
|
43 |
, setBoth
|
|
44 |
, setMovable
|
|
45 |
, specOf
|
|
46 |
, shrinkByType
|
|
47 |
, localStorageTemplates
|
|
48 |
, hasSecondary
|
|
49 |
, requiredNodes
|
|
50 |
, allNodes
|
|
51 |
, usesLocalStorage
|
|
52 |
) where
|
|
30 |
( Instance(..) |
|
31 |
, AssocList |
|
32 |
, List |
|
33 |
, create |
|
34 |
, instanceRunning |
|
35 |
, instanceOffline |
|
36 |
, instanceDown |
|
37 |
, applyIfOnline |
|
38 |
, setIdx |
|
39 |
, setName |
|
40 |
, setAlias |
|
41 |
, setPri |
|
42 |
, setSec |
|
43 |
, setBoth |
|
44 |
, setMovable |
|
45 |
, specOf |
|
46 |
, shrinkByType |
|
47 |
, localStorageTemplates |
|
48 |
, hasSecondary |
|
49 |
, requiredNodes |
|
50 |
, allNodes |
|
51 |
, usesLocalStorage |
|
52 |
) where |
|
53 | 53 |
|
54 | 54 |
import qualified Ganeti.HTools.Types as T |
55 | 55 |
import qualified Ganeti.HTools.Container as Container |
... | ... | |
61 | 61 |
|
62 | 62 |
-- | The instance type. |
63 | 63 |
data Instance = Instance |
64 |
{ name :: String -- ^ The instance name
|
|
65 |
, alias :: String -- ^ The shortened name
|
|
66 |
, mem :: Int -- ^ Memory of the instance
|
|
67 |
, dsk :: Int -- ^ Disk size of instance
|
|
68 |
, vcpus :: Int -- ^ Number of VCPUs
|
|
69 |
, runSt :: T.InstanceStatus -- ^ Original run status
|
|
70 |
, pNode :: T.Ndx -- ^ Original primary node
|
|
71 |
, sNode :: T.Ndx -- ^ Original secondary node
|
|
72 |
, idx :: T.Idx -- ^ Internal index
|
|
73 |
, util :: T.DynUtil -- ^ Dynamic resource usage
|
|
74 |
, movable :: Bool -- ^ Can and should the instance be moved?
|
|
75 |
, autoBalance :: Bool -- ^ Is the instance auto-balanced?
|
|
76 |
, tags :: [String] -- ^ List of instance tags
|
|
77 |
, diskTemplate :: T.DiskTemplate -- ^ The disk template of the instance
|
|
78 |
} deriving (Show, Read)
|
|
64 |
{ name :: String -- ^ The instance name |
|
65 |
, alias :: String -- ^ The shortened name |
|
66 |
, mem :: Int -- ^ Memory of the instance |
|
67 |
, dsk :: Int -- ^ Disk size of instance |
|
68 |
, vcpus :: Int -- ^ Number of VCPUs |
|
69 |
, runSt :: T.InstanceStatus -- ^ Original run status |
|
70 |
, pNode :: T.Ndx -- ^ Original primary node |
|
71 |
, sNode :: T.Ndx -- ^ Original secondary node |
|
72 |
, idx :: T.Idx -- ^ Internal index |
|
73 |
, util :: T.DynUtil -- ^ Dynamic resource usage |
|
74 |
, movable :: Bool -- ^ Can and should the instance be moved? |
|
75 |
, autoBalance :: Bool -- ^ Is the instance auto-balanced? |
|
76 |
, tags :: [String] -- ^ List of instance tags |
|
77 |
, diskTemplate :: T.DiskTemplate -- ^ The disk template of the instance |
|
78 |
} deriving (Show, Read) |
|
79 | 79 |
|
80 | 80 |
instance T.Element Instance where |
81 |
nameOf = name
|
|
82 |
idxOf = idx
|
|
83 |
setAlias = setAlias
|
|
84 |
setIdx = setIdx
|
|
85 |
allNames n = [name n, alias n]
|
|
81 |
nameOf = name |
|
82 |
idxOf = idx |
|
83 |
setAlias = setAlias |
|
84 |
setIdx = setIdx |
|
85 |
allNames n = [name n, alias n] |
|
86 | 86 |
|
87 | 87 |
-- | Check if instance is running. |
88 | 88 |
instanceRunning :: Instance -> Bool |
89 | 89 |
instanceRunning (Instance {runSt = T.Running}) = True |
90 | 90 |
instanceRunning (Instance {runSt = T.ErrorUp}) = True |
91 |
instanceRunning _ = False
|
|
91 |
instanceRunning _ = False |
|
92 | 92 |
|
93 | 93 |
-- | Check if instance is offline. |
94 | 94 |
instanceOffline :: Instance -> Bool |
95 | 95 |
instanceOffline (Instance {runSt = T.AdminOffline}) = True |
96 |
instanceOffline _ = False
|
|
96 |
instanceOffline _ = False |
|
97 | 97 |
|
98 | 98 |
-- | Check if instance is down. |
99 | 99 |
instanceDown :: Instance -> Bool |
... | ... | |
141 | 141 |
-> [String] -> Bool -> T.Ndx -> T.Ndx -> T.DiskTemplate -> Instance |
142 | 142 |
create name_init mem_init dsk_init vcpus_init run_init tags_init |
143 | 143 |
auto_balance_init pn sn dt = |
144 |
Instance { name = name_init
|
|
145 |
, alias = name_init
|
|
146 |
, mem = mem_init
|
|
147 |
, dsk = dsk_init
|
|
148 |
, vcpus = vcpus_init
|
|
149 |
, runSt = run_init
|
|
150 |
, pNode = pn
|
|
151 |
, sNode = sn
|
|
152 |
, idx = -1
|
|
153 |
, util = T.baseUtil
|
|
154 |
, tags = tags_init
|
|
155 |
, movable = supportsMoves dt
|
|
156 |
, autoBalance = auto_balance_init
|
|
157 |
, diskTemplate = dt
|
|
158 |
}
|
|
144 |
Instance { name = name_init |
|
145 |
, alias = name_init |
|
146 |
, mem = mem_init |
|
147 |
, dsk = dsk_init |
|
148 |
, vcpus = vcpus_init |
|
149 |
, runSt = run_init |
|
150 |
, pNode = pn |
|
151 |
, sNode = sn |
|
152 |
, idx = -1 |
|
153 |
, util = T.baseUtil |
|
154 |
, tags = tags_init |
|
155 |
, movable = supportsMoves dt |
|
156 |
, autoBalance = auto_balance_init |
|
157 |
, diskTemplate = dt |
|
158 |
} |
|
159 | 159 |
|
160 | 160 |
-- | Changes the index. |
161 | 161 |
-- |
... | ... | |
228 | 228 |
-- | Return the spec of an instance. |
229 | 229 |
specOf :: Instance -> T.RSpec |
230 | 230 |
specOf Instance { mem = m, dsk = d, vcpus = c } = |
231 |
T.RSpec { T.rspecCpu = c, T.rspecMem = m, T.rspecDsk = d }
|
|
231 |
T.RSpec { T.rspecCpu = c, T.rspecMem = m, T.rspecDsk = d } |
|
232 | 232 |
|
233 | 233 |
-- | Checks whether the instance uses a secondary node. |
234 | 234 |
-- |
b/htools/Ganeti/HTools/JSON.hs | ||
---|---|---|
22 | 22 |
-} |
23 | 23 |
|
24 | 24 |
module Ganeti.HTools.JSON |
25 |
( fromJResult
|
|
26 |
, readEitherString
|
|
27 |
, JSRecord
|
|
28 |
, loadJSArray
|
|
29 |
, fromObj
|
|
30 |
, maybeFromObj
|
|
31 |
, fromObjWithDefault
|
|
32 |
, fromJVal
|
|
33 |
, asJSObject
|
|
34 |
, asObjectList
|
|
35 |
)
|
|
36 |
where
|
|
25 |
( fromJResult |
|
26 |
, readEitherString |
|
27 |
, JSRecord |
|
28 |
, loadJSArray |
|
29 |
, fromObj |
|
30 |
, maybeFromObj |
|
31 |
, fromObjWithDefault |
|
32 |
, fromJVal |
|
33 |
, asJSObject |
|
34 |
, asObjectList |
|
35 |
) |
|
36 |
where |
|
37 | 37 |
|
38 | 38 |
import Control.Monad (liftM) |
39 | 39 |
import Data.Maybe (fromMaybe) |
... | ... | |
57 | 57 |
-- context of the current monad. |
58 | 58 |
readEitherString :: (Monad m) => J.JSValue -> m String |
59 | 59 |
readEitherString v = |
60 |
case v of
|
|
61 |
J.JSString s -> return $ J.fromJSString s
|
|
62 |
_ -> fail "Wrong JSON type"
|
|
60 |
case v of |
|
61 |
J.JSString s -> return $ J.fromJSString s |
|
62 |
_ -> fail "Wrong JSON type" |
|
63 | 63 |
|
64 | 64 |
-- | Converts a JSON message into an array of JSON objects. |
65 | 65 |
loadJSArray :: (Monad m) |
... | ... | |
71 | 71 |
-- | Reads the value of a key in a JSON object. |
72 | 72 |
fromObj :: (J.JSON a, Monad m) => JSRecord -> String -> m a |
73 | 73 |
fromObj o k = |
74 |
case lookup k o of
|
|
75 |
Nothing -> fail $ printf "key '%s' not found, object contains only %s"
|
|
76 |
k (show (map fst o))
|
|
77 |
Just val -> fromKeyValue k val
|
|
74 |
case lookup k o of |
|
75 |
Nothing -> fail $ printf "key '%s' not found, object contains only %s" |
|
76 |
k (show (map fst o)) |
|
77 |
Just val -> fromKeyValue k val |
|
78 | 78 |
|
79 | 79 |
-- | Reads the value of an optional key in a JSON object. |
80 | 80 |
maybeFromObj :: (J.JSON a, Monad m) => |
81 | 81 |
JSRecord -> String -> m (Maybe a) |
82 | 82 |
maybeFromObj o k = |
83 |
case lookup k o of
|
|
84 |
Nothing -> return Nothing
|
|
85 |
Just val -> liftM Just (fromKeyValue k val)
|
|
83 |
case lookup k o of |
|
84 |
Nothing -> return Nothing |
|
85 |
Just val -> liftM Just (fromKeyValue k val) |
|
86 | 86 |
|
87 | 87 |
-- | Reads the value of a key in a JSON object with a default if missing. |
88 | 88 |
fromObjWithDefault :: (J.JSON a, Monad m) => |
... | ... | |
100 | 100 |
-- | Small wrapper over readJSON. |
101 | 101 |
fromJVal :: (Monad m, J.JSON a) => J.JSValue -> m a |
102 | 102 |
fromJVal v = |
103 |
case J.readJSON v of
|
|
104 |
J.Error s -> fail ("Cannot convert value '" ++ show v ++
|
|
105 |
"', error: " ++ s)
|
|
106 |
J.Ok x -> return x
|
|
103 |
case J.readJSON v of |
|
104 |
J.Error s -> fail ("Cannot convert value '" ++ show v ++ |
|
105 |
"', error: " ++ s) |
|
106 |
J.Ok x -> return x |
|
107 | 107 |
|
108 | 108 |
-- | Converts a JSON value into a JSON object. |
109 | 109 |
asJSObject :: (Monad m) => J.JSValue -> m (J.JSObject J.JSValue) |
b/htools/Ganeti/HTools/Loader.hs | ||
---|---|---|
27 | 27 |
-} |
28 | 28 |
|
29 | 29 |
module Ganeti.HTools.Loader |
30 |
( mergeData
|
|
31 |
, checkData
|
|
32 |
, assignIndices
|
|
33 |
, lookupName
|
|
34 |
, goodLookupResult
|
|
35 |
, lookupNode
|
|
36 |
, lookupInstance
|
|
37 |
, lookupGroup
|
|
38 |
, commonSuffix
|
|
39 |
, RqType(..)
|
|
40 |
, Request(..)
|
|
41 |
, ClusterData(..)
|
|
42 |
, emptyCluster
|
|
43 |
, compareNameComponent
|
|
44 |
, prefixMatch
|
|
45 |
, LookupResult(..)
|
|
46 |
, MatchPriority(..)
|
|
47 |
) where
|
|
30 |
( mergeData |
|
31 |
, checkData |
|
32 |
, assignIndices |
|
33 |
, lookupName |
|
34 |
, goodLookupResult |
|
35 |
, lookupNode |
|
36 |
, lookupInstance |
|
37 |
, lookupGroup |
|
38 |
, commonSuffix |
|
39 |
, RqType(..) |
|
40 |
, Request(..) |
|
41 |
, ClusterData(..) |
|
42 |
, emptyCluster |
|
43 |
, compareNameComponent |
|
44 |
, prefixMatch |
|
45 |
, LookupResult(..) |
|
46 |
, MatchPriority(..) |
|
47 |
) where |
|
48 | 48 |
|
49 | 49 |
import Data.List |
50 | 50 |
import Data.Function |
... | ... | |
74 | 74 |
|
75 | 75 |
-} |
76 | 76 |
data RqType |
77 |
= Allocate Instance.Instance Int -- ^ A new instance allocation
|
|
78 |
| Relocate Idx Int [Ndx] -- ^ Choose a new secondary node
|
|
79 |
| NodeEvacuate [Idx] EvacMode -- ^ node-evacuate mode
|
|
80 |
| ChangeGroup [Gdx] [Idx] -- ^ Multi-relocate mode
|
|
77 |
= Allocate Instance.Instance Int -- ^ A new instance allocation |
|
78 |
| Relocate Idx Int [Ndx] -- ^ Choose a new secondary node |
|
79 |
| NodeEvacuate [Idx] EvacMode -- ^ node-evacuate mode |
|
80 |
| ChangeGroup [Gdx] [Idx] -- ^ Multi-relocate mode |
|
81 | 81 |
deriving (Show, Read) |
82 | 82 |
|
83 | 83 |
-- | A complete request, as received from Ganeti. |
84 | 84 |
data Request = Request RqType ClusterData |
85 |
deriving (Show, Read) |
|
85 |
deriving (Show, Read)
|
|
86 | 86 |
|
87 | 87 |
-- | The cluster state. |
88 | 88 |
data ClusterData = ClusterData |
89 |
{ cdGroups :: Group.List -- ^ The node group list
|
|
90 |
, cdNodes :: Node.List -- ^ The node list
|
|
91 |
, cdInstances :: Instance.List -- ^ The instance list
|
|
92 |
, cdTags :: [String] -- ^ The cluster tags
|
|
93 |
} deriving (Show, Read)
|
|
89 |
{ cdGroups :: Group.List -- ^ The node group list |
|
90 |
, cdNodes :: Node.List -- ^ The node list |
|
91 |
, cdInstances :: Instance.List -- ^ The instance list |
|
92 |
, cdTags :: [String] -- ^ The cluster tags |
|
93 |
} deriving (Show, Read) |
|
94 | 94 |
|
95 | 95 |
-- | The priority of a match in a lookup result. |
96 | 96 |
data MatchPriority = ExactMatch |
... | ... | |
101 | 101 |
|
102 | 102 |
-- | The result of a name lookup in a list. |
103 | 103 |
data LookupResult = LookupResult |
104 |
{ lrMatchPriority :: MatchPriority -- ^ The result type
|
|
105 |
-- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
|
|
106 |
, lrContent :: String
|
|
107 |
} deriving (Show, Read)
|
|
104 |
{ lrMatchPriority :: MatchPriority -- ^ The result type |
|
105 |
-- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise |
|
106 |
, lrContent :: String |
|
107 |
} deriving (Show, Read) |
|
108 | 108 |
|
109 | 109 |
-- | Lookup results have an absolute preference ordering. |
110 | 110 |
instance Eq LookupResult where |
... | ... | |
122 | 122 |
-- | Lookups a node into an assoc list. |
123 | 123 |
lookupNode :: (Monad m) => NameAssoc -> String -> String -> m Ndx |
124 | 124 |
lookupNode ktn inst node = |
125 |
case M.lookup node ktn of
|
|
126 |
Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
|
|
127 |
Just idx -> return idx
|
|
125 |
case M.lookup node ktn of |
|
126 |
Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst |
|
127 |
Just idx -> return idx |
|
128 | 128 |
|
129 | 129 |
-- | Lookups an instance into an assoc list. |
130 | 130 |
lookupInstance :: (Monad m) => NameAssoc -> String -> m Idx |
131 | 131 |
lookupInstance kti inst = |
132 |
case M.lookup inst kti of
|
|
133 |
Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
|
|
134 |
Just idx -> return idx
|
|
132 |
case M.lookup inst kti of |
|
133 |
Nothing -> fail $ "Unknown instance '" ++ inst ++ "'" |
|
134 |
Just idx -> return idx |
|
135 | 135 |
|
136 | 136 |
-- | Lookups a group into an assoc list. |
137 | 137 |
lookupGroup :: (Monad m) => NameAssoc -> String -> String -> m Gdx |
138 | 138 |
lookupGroup ktg nname gname = |
139 |
case M.lookup gname ktg of
|
|
140 |
Nothing -> fail $ "Unknown group '" ++ gname ++ "' for node " ++ nname
|
|
141 |
Just idx -> return idx
|
|
139 |
case M.lookup gname ktg of |
|
140 |
Nothing -> fail $ "Unknown group '" ++ gname ++ "' for node " ++ nname |
|
141 |
Just idx -> return idx |
|
142 | 142 |
|
143 | 143 |
-- | Check for prefix matches in names. |
144 | 144 |
-- Implemented in Ganeti core utils.text.MatchNameComponent |
... | ... | |
206 | 206 |
-> Instance.Instance |
207 | 207 |
-> Node.List |
208 | 208 |
fixNodes accu inst = |
209 |
let |
|
210 |
pdx = Instance.pNode inst |
|
211 |
sdx = Instance.sNode inst |
|
212 |
pold = Container.find pdx accu |
|
213 |
pnew = Node.setPri pold inst |
|
214 |
ac2 = Container.add pdx pnew accu |
|
215 |
in |
|
216 |
if sdx /= Node.noSecondary |
|
217 |
then let sold = Container.find sdx accu |
|
218 |
snew = Node.setSec sold inst |
|
219 |
in Container.add sdx snew ac2 |
|
220 |
else ac2 |
|
209 |
let pdx = Instance.pNode inst |
|
210 |
sdx = Instance.sNode inst |
|
211 |
pold = Container.find pdx accu |
|
212 |
pnew = Node.setPri pold inst |
|
213 |
ac2 = Container.add pdx pnew accu |
|
214 |
in if sdx /= Node.noSecondary |
|
215 |
then let sold = Container.find sdx accu |
|
216 |
snew = Node.setSec sold inst |
|
217 |
in Container.add sdx snew ac2 |
|
218 |
else ac2 |
|
221 | 219 |
|
222 | 220 |
-- | Remove non-selected tags from the exclusion list. |
223 | 221 |
filterExTags :: [String] -> Instance.Instance -> Instance.Instance |
224 | 222 |
filterExTags tl inst = |
225 |
let old_tags = Instance.tags inst |
|
226 |
new_tags = filter (\tag -> any (`isPrefixOf` tag) tl) |
|
227 |
old_tags |
|
228 |
in inst { Instance.tags = new_tags } |
|
223 |
let old_tags = Instance.tags inst |
|
224 |
new_tags = filter (\tag -> any (`isPrefixOf` tag) tl) old_tags |
|
225 |
in inst { Instance.tags = new_tags } |
|
229 | 226 |
|
230 | 227 |
-- | Update the movable attribute. |
231 | 228 |
updateMovable :: [String] -- ^ Selected instances (if not empty) |
... | ... | |
233 | 230 |
-> Instance.Instance -- ^ Target Instance |
234 | 231 |
-> Instance.Instance -- ^ Target Instance with updated attribute |
235 | 232 |
updateMovable selinsts exinsts inst = |
236 |
if Instance.sNode inst == Node.noSecondary ||
|
|
237 |
Instance.name inst `elem` exinsts ||
|
|
238 |
not (null selinsts || Instance.name inst `elem` selinsts)
|
|
233 |
if Instance.sNode inst == Node.noSecondary || |
|
234 |
Instance.name inst `elem` exinsts || |
|
235 |
not (null selinsts || Instance.name inst `elem` selinsts) |
|
239 | 236 |
then Instance.setMovable inst False |
240 | 237 |
else inst |
241 | 238 |
|
... | ... | |
244 | 241 |
longestDomain :: [String] -> String |
245 | 242 |
longestDomain [] = "" |
246 | 243 |
longestDomain (x:xs) = |
247 |
foldr (\ suffix accu -> if all (isSuffixOf suffix) xs
|
|
248 |
then suffix
|
|
249 |
else accu)
|
|
250 |
"" $ filter (isPrefixOf ".") (tails x) |
|
244 |
foldr (\ suffix accu -> if all (isSuffixOf suffix) xs |
|
245 |
then suffix |
|
246 |
else accu) |
|
247 |
"" $ filter (isPrefixOf ".") (tails x)
|
|
251 | 248 |
|
252 | 249 |
-- | Extracts the exclusion tags from the cluster configuration. |
253 | 250 |
extractExTags :: [String] -> [String] |
254 | 251 |
extractExTags = |
255 |
map (drop (length exTagsPrefix)) .
|
|
256 |
filter (isPrefixOf exTagsPrefix)
|
|
252 |
map (drop (length exTagsPrefix)) . |
|
253 |
filter (isPrefixOf exTagsPrefix) |
|
257 | 254 |
|
258 | 255 |
-- | Extracts the common suffix from node\/instance names. |
259 | 256 |
commonSuffix :: Node.List -> Instance.List -> String |
260 | 257 |
commonSuffix nl il = |
261 |
let node_names = map Node.name $ Container.elems nl
|
|
262 |
inst_names = map Instance.name $ Container.elems il
|
|
263 |
in longestDomain (node_names ++ inst_names)
|
|
258 |
let node_names = map Node.name $ Container.elems nl |
|
259 |
inst_names = map Instance.name $ Container.elems il |
|
260 |
in longestDomain (node_names ++ inst_names) |
|
264 | 261 |
|
265 | 262 |
-- | Initializer function that loads the data from a node and instance |
266 | 263 |
-- list and massages it into the correct format. |
... | ... | |
328 | 325 |
-- | Compute the amount of memory used by primary instances on a node. |
329 | 326 |
nodeImem :: Node.Node -> Instance.List -> Int |
330 | 327 |
nodeImem node il = |
331 |
let rfind = flip Container.find il
|
|
332 |
il' = map rfind $ Node.pList node
|
|
333 |
oil' = filter (not . Instance.instanceOffline) il'
|
|
334 |
in sum . map Instance.mem $ oil'
|
|
328 |
let rfind = flip Container.find il |
|
329 |
il' = map rfind $ Node.pList node |
|
330 |
oil' = filter (not . Instance.instanceOffline) il' |
|
331 |
in sum . map Instance.mem $ oil' |
|
335 | 332 |
|
336 | 333 |
|
337 | 334 |
-- | Compute the amount of disk used by instances on a node (either primary |
338 | 335 |
-- or secondary). |
339 | 336 |
nodeIdsk :: Node.Node -> Instance.List -> Int |
340 | 337 |
nodeIdsk node il = |
341 |
let rfind = flip Container.find il |
|
342 |
in sum . map (Instance.dsk . rfind) |
|
343 |
$ Node.pList node ++ Node.sList node |
|
338 |
let rfind = flip Container.find il |
|
339 |
in sum . map (Instance.dsk . rfind) |
|
340 |
$ Node.pList node ++ Node.sList node |
b/htools/Ganeti/HTools/Luxi.hs | ||
---|---|---|
24 | 24 |
-} |
25 | 25 |
|
26 | 26 |
module Ganeti.HTools.Luxi |
27 |
( |
|
28 |
loadData |
|
29 |
, parseData |
|
30 |
) where |
|
27 |
( loadData |
|
28 |
, parseData |
|
29 |
) where |
|
31 | 30 |
|
32 | 31 |
import qualified Control.Exception as E |
33 | 32 |
import Text.JSON.Types |
... | ... | |
53 | 52 |
parseQueryField :: (Monad m) => JSValue -> m (JSValue, JSValue) |
54 | 53 |
parseQueryField (JSArray [status, result]) = return (status, result) |
55 | 54 |
parseQueryField o = |
56 |
fail $ "Invalid query field, expected (status, value) but got " ++ show o
|
|
55 |
fail $ "Invalid query field, expected (status, value) but got " ++ show o |
|
57 | 56 |
|
58 | 57 |
-- | Parse a result row. |
59 | 58 |
parseQueryRow :: (Monad m) => JSValue -> m [(JSValue, JSValue)] |
60 | 59 |
parseQueryRow (JSArray arr) = mapM parseQueryField arr |
61 | 60 |
parseQueryRow o = |
62 |
fail $ "Invalid query row result, expected array but got " ++ show o
|
|
61 |
fail $ "Invalid query row result, expected array but got " ++ show o |
|
63 | 62 |
|
64 | 63 |
-- | Parse an overall query result and get the [(status, value)] list |
65 | 64 |
-- for each element queried. |
66 | 65 |
parseQueryResult :: (Monad m) => JSValue -> m [[(JSValue, JSValue)]] |
67 | 66 |
parseQueryResult (JSArray arr) = mapM parseQueryRow arr |
68 | 67 |
parseQueryResult o = |
69 |
fail $ "Invalid query result, expected array but got " ++ show o
|
|
68 |
fail $ "Invalid query result, expected array but got " ++ show o |
|
70 | 69 |
|
71 | 70 |
-- | Prepare resulting output as parsers expect it. |
72 | 71 |
extractArray :: (Monad m) => JSValue -> m [[(JSValue, JSValue)]] |
... | ... | |
76 | 75 |
-- | Testing result status for more verbose error message. |
77 | 76 |
fromJValWithStatus :: (Text.JSON.JSON a, Monad m) => (JSValue, JSValue) -> m a |
78 | 77 |
fromJValWithStatus (st, v) = do |
79 |
st' <- fromJVal st
|
|
80 |
L.checkRS st' v >>= fromJVal
|
|
78 |
st' <- fromJVal st |
|
79 |
L.checkRS st' v >>= fromJVal |
|
81 | 80 |
|
82 | 81 |
-- | Annotate errors when converting values with owner/attribute for |
83 | 82 |
-- better debugging. |
... | ... | |
88 | 87 |
-> (JSValue, JSValue) -- ^ The value we're trying to convert |
89 | 88 |
-> Result a -- ^ The annotated result |
90 | 89 |
genericConvert otype oname oattr = |
91 |
annotateResult (otype ++ " '" ++ oname ++
|
|
92 |
"', error while reading attribute '" ++
|
|
93 |
oattr ++ "'") . fromJValWithStatus
|
|
90 |
annotateResult (otype ++ " '" ++ oname ++ |
|
91 |
"', error while reading attribute '" ++ |
|
92 |
oattr ++ "'") . fromJValWithStatus |
|
94 | 93 |
|
95 | 94 |
-- * Data querying functionality |
96 | 95 |
|
... | ... | |
104 | 103 |
-- | The input data for instance query. |
105 | 104 |
queryInstancesMsg :: L.LuxiOp |
106 | 105 |
queryInstancesMsg = |
107 |
L.Query L.QRInstance ["name", "disk_usage", "be/memory", "be/vcpus",
|
|
108 |
"status", "pnode", "snodes", "tags", "oper_ram",
|
|
109 |
"be/auto_balance", "disk_template"] ()
|
|
106 |
L.Query L.QRInstance ["name", "disk_usage", "be/memory", "be/vcpus", |
|
107 |
"status", "pnode", "snodes", "tags", "oper_ram", |
|
108 |
"be/auto_balance", "disk_template"] () |
|
110 | 109 |
|
111 | 110 |
-- | The input data for cluster query. |
112 | 111 |
queryClusterInfoMsg :: L.LuxiOp |
b/htools/Ganeti/HTools/PeerMap.hs | ||
---|---|---|
8 | 8 |
|
9 | 9 |
{- |
10 | 10 |
|
11 |
Copyright (C) 2009 Google Inc. |
|
11 |
Copyright (C) 2009, 2011 Google Inc.
|
|
12 | 12 |
|
13 | 13 |
This program is free software; you can redistribute it and/or modify |
14 | 14 |
it under the terms of the GNU General Public License as published by |
... | ... | |
28 | 28 |
-} |
29 | 29 |
|
30 | 30 |
module Ganeti.HTools.PeerMap |
31 |
( PeerMap
|
|
32 |
, Key
|
|
33 |
, Elem
|
|
34 |
, empty
|
|
35 |
, accumArray
|
|
36 |
, Ganeti.HTools.PeerMap.find
|
|
37 |
, add
|
|
38 |
, remove
|
|
39 |
, maxElem
|
|
40 |
) where
|
|
31 |
( PeerMap |
|
32 |
, Key |
|
33 |
, Elem |
|
34 |
, empty |
|
35 |
, accumArray |
|
36 |
, Ganeti.HTools.PeerMap.find |
|
37 |
, add |
|
38 |
, remove |
|
39 |
, maxElem |
|
40 |
) where |
|
41 | 41 |
|
42 | 42 |
import Data.Maybe (fromMaybe) |
43 | 43 |
import Data.List |
... | ... | |
70 | 70 |
-- | Add or update (via a custom function) an element. |
71 | 71 |
addWith :: (Elem -> Elem -> Elem) -> Key -> Elem -> PeerMap -> PeerMap |
72 | 72 |
addWith fn k v lst = |
73 |
case lookup k lst of
|
|
74 |
Nothing -> insertBy pmCompare (k, v) lst
|
|
75 |
Just o -> insertBy pmCompare (k, fn o v) (remove k lst)
|
|
73 |
case lookup k lst of |
|
74 |
Nothing -> insertBy pmCompare (k, v) lst |
|
75 |
Just o -> insertBy pmCompare (k, fn o v) (remove k lst) |
|
76 | 76 |
|
77 | 77 |
-- | Create a PeerMap from an association list, with possible duplicates. |
78 | 78 |
accumArray :: (Elem -> Elem -> Elem) -- ^ function used to merge the elements |
b/htools/Ganeti/HTools/Program/Hail.hs | ||
---|---|---|
39 | 39 |
-- | Options list and functions. |
40 | 40 |
options :: [OptType] |
41 | 41 |
options = |
42 |
[ oPrintNodes
|
|
43 |
, oSaveCluster
|
|
44 |
, oDataFile
|
|
45 |
, oNodeSim
|
|
46 |
, oVerbose
|
|
47 |
, oShowVer
|
|
48 |
, oShowHelp
|
|
49 |
]
|
|
42 |
[ oPrintNodes |
|
43 |
, oSaveCluster |
|
44 |
, oDataFile |
|
45 |
, oNodeSim |
|
46 |
, oVerbose |
|
47 |
, oShowVer |
|
48 |
, oShowHelp |
|
49 |
] |
|
50 | 50 |
|
51 | 51 |
-- | Main function. |
52 | 52 |
main :: IO () |
b/htools/Ganeti/HTools/Program/Hscan.hs | ||
---|---|---|
49 | 49 |
-- | Options list and functions. |
50 | 50 |
options :: [OptType] |
51 | 51 |
options = |
52 |
[ oPrintNodes
|
|
53 |
, oOutputDir
|
|
54 |
, oLuxiSocket
|
|
55 |
, oVerbose
|
|
56 |
, oNoHeaders
|
|
57 |
, oShowVer
|
|
58 |
, oShowHelp
|
|
59 |
]
|
|
52 |
[ oPrintNodes |
|
53 |
, oOutputDir |
|
54 |
, oLuxiSocket |
|
55 |
, oVerbose |
|
56 |
, oNoHeaders |
|
57 |
, oShowVer |
|
58 |
, oShowHelp |
|
59 |
] |
|
60 | 60 |
|
61 | 61 |
-- | Return a one-line summary of cluster state. |
62 | 62 |
printCluster :: Node.List -> Instance.List |
63 | 63 |
-> String |
64 | 64 |
printCluster nl il = |
65 |
let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il |
|
66 |
ccv = Cluster.compCV nl |
|
67 |
nodes = Container.elems nl |
|
68 |
insts = Container.elems il |
|
69 |
t_ram = sum . map Node.tMem $ nodes |
|
70 |
t_dsk = sum . map Node.tDsk $ nodes |
|
71 |
f_ram = sum . map Node.fMem $ nodes |
|
72 |
f_dsk = sum . map Node.fDsk $ nodes |
|
73 |
in |
|
74 |
printf "%5d %5d %5d %5d %6.0f %6d %6.0f %6d %.8f" |
|
75 |
(length nodes) (length insts) |
|
76 |
(length bad_nodes) (length bad_instances) |
|
77 |
t_ram f_ram |
|
78 |
(t_dsk / 1024) (f_dsk `div` 1024) |
|
79 |
ccv |
|
80 |
|
|
65 |
let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il |
|
66 |
ccv = Cluster.compCV nl |
|
67 |
nodes = Container.elems nl |
|
68 |
insts = Container.elems il |
|
69 |
t_ram = sum . map Node.tMem $ nodes |
|
70 |
t_dsk = sum . map Node.tDsk $ nodes |
|
71 |
f_ram = sum . map Node.fMem $ nodes |
|
72 |
f_dsk = sum . map Node.fDsk $ nodes |
|
73 |
in printf "%5d %5d %5d %5d %6.0f %6d %6.0f %6d %.8f" |
|
74 |
(length nodes) (length insts) |
|
75 |
(length bad_nodes) (length bad_instances) |
|
76 |
t_ram f_ram (t_dsk / 1024) (f_dsk `div` 1024) ccv |
|
81 | 77 |
|
82 | 78 |
-- | Replace slashes with underscore for saving to filesystem. |
83 | 79 |
fixSlash :: String -> String |
84 | 80 |
fixSlash = map (\x -> if x == '/' then '_' else x) |
85 | 81 |
|
86 |
|
|
87 | 82 |
-- | Generates serialized data from loader input. |
88 | 83 |
processData :: ClusterData -> Result ClusterData |
89 | 84 |
processData input_data = do |
b/htools/Ganeti/HTools/QCHelper.hs | ||
---|---|---|
26 | 26 |
-} |
27 | 27 |
|
28 | 28 |
module Ganeti.HTools.QCHelper |
29 |
( testSuite
|
|
30 |
) where
|
|
29 |
( testSuite |
|
30 |
) where |
|
31 | 31 |
|
32 | 32 |
import Test.QuickCheck |
33 | 33 |
import Language.Haskell.TH |
b/htools/Ganeti/HTools/Rapi.hs | ||
---|---|---|
26 | 26 |
{-# LANGUAGE BangPatterns, CPP #-} |
27 | 27 |
|
28 | 28 |
module Ganeti.HTools.Rapi |
29 |
( |
|
30 |
loadData |
|
31 |
, parseData |
|
32 |
) where |
|
29 |
( loadData |
|
30 |
, parseData |
|
31 |
) where |
|
33 | 32 |
|
34 | 33 |
import Data.Maybe (fromMaybe) |
35 | 34 |
#ifndef NO_CURL |
... | ... | |
76 | 75 |
-- | Append the default port if not passed in. |
77 | 76 |
formatHost :: String -> String |
78 | 77 |
formatHost master = |
79 |
if ':' `elem` master then master |
|
78 |
if ':' `elem` master |
|
79 |
then master |
|
80 | 80 |
else "https://" ++ master ++ ":" ++ show C.defaultRapiPort |
81 | 81 |
|
82 | 82 |
-- | Parse a instance list in JSON format. |
... | ... | |
84 | 84 |
-> String |
85 | 85 |
-> Result [(String, Instance.Instance)] |
86 | 86 |
getInstances ktn body = |
87 |
loadJSArray "Parsing instance data" body >>=
|
|
88 |
mapM (parseInstance ktn . fromJSObject)
|
|
87 |
loadJSArray "Parsing instance data" body >>= |
|
88 |
mapM (parseInstance ktn . fromJSObject) |
|
89 | 89 |
|
90 | 90 |
-- | Parse a node list in JSON format. |
91 | 91 |
getNodes :: NameAssoc -> String -> Result [(String, Node.Node)] |
92 | 92 |
getNodes ktg body = loadJSArray "Parsing node data" body >>= |
93 |
mapM (parseNode ktg . fromJSObject) |
|
93 |
mapM (parseNode ktg . fromJSObject)
|
|
94 | 94 |
|
95 | 95 |
-- | Parse a group list in JSON format. |
96 | 96 |
getGroups :: String -> Result [(String, Group.Group)] |
97 | 97 |
getGroups body = loadJSArray "Parsing group data" body >>= |
98 |
mapM (parseGroup . fromJSObject) |
|
98 |
mapM (parseGroup . fromJSObject)
|
|
99 | 99 |
|
100 | 100 |
-- | Construct an instance from a JSON object. |
101 | 101 |
parseInstance :: NameAssoc |
b/htools/Ganeti/HTools/Simu.hs | ||
---|---|---|
26 | 26 |
-} |
27 | 27 |
|
28 | 28 |
module Ganeti.HTools.Simu |
29 |
( |
|
30 |
loadData |
|
31 |
, parseData |
|
32 |
) where |
|
29 |
( loadData |
|
30 |
, parseData |
|
31 |
) where |
|
33 | 32 |
|
34 | 33 |
import Control.Monad (mplus) |
35 | 34 |
import Text.Printf (printf) |
... | ... | |
52 | 51 |
-- | Parse the string description into nodes. |
53 | 52 |
parseDesc :: String -> Result (AllocPolicy, Int, Int, Int, Int) |
54 | 53 |
parseDesc desc = |
55 |
case sepSplit ',' desc of
|
|
56 |
[a, n, d, m, c] -> do
|
|
57 |
apol <- allocPolicyFromRaw a `mplus` apolAbbrev a
|
|
58 |
ncount <- tryRead "node count" n
|
|
59 |
disk <- annotateResult "disk size" (parseUnit d)
|
|
60 |
mem <- annotateResult "memory size" (parseUnit m)
|
|
61 |
cpu <- tryRead "cpu count" c
|
|
62 |
return (apol, ncount, disk, mem, cpu)
|
|
63 |
es -> fail $ printf
|
|
64 |
"Invalid cluster specification, expected 5 comma-separated\
|
|
65 |
\ sections (allocation policy, node count, disk size,\
|
|
66 |
\ memory size, number of CPUs) but got %d: '%s'" (length es) desc
|
|
54 |
case sepSplit ',' desc of |
|
55 |
[a, n, d, m, c] -> do |
|
56 |
apol <- allocPolicyFromRaw a `mplus` apolAbbrev a |
|
57 |
ncount <- tryRead "node count" n |
|
58 |
disk <- annotateResult "disk size" (parseUnit d) |
|
59 |
mem <- annotateResult "memory size" (parseUnit m) |
|
60 |
cpu <- tryRead "cpu count" c |
|
61 |
return (apol, ncount, disk, mem, cpu) |
|
62 |
es -> fail $ printf |
|
63 |
"Invalid cluster specification, expected 5 comma-separated\ |
|
64 |
\ sections (allocation policy, node count, disk size,\ |
|
65 |
\ memory size, number of CPUs) but got %d: '%s'" (length es) desc |
|
67 | 66 |
|
68 | 67 |
-- | Creates a node group with the given specifications. |
69 | 68 |
createGroup :: Int -- ^ The group index |
... | ... | |
72 | 71 |
createGroup grpIndex spec = do |
73 | 72 |
(apol, ncount, disk, mem, cpu) <- parseDesc spec |
74 | 73 |
let nodes = map (\idx -> |
75 |
Node.create (printf "node-%02d-%03d" grpIndex idx)
|
|
76 |
(fromIntegral mem) 0 mem
|
|
77 |
(fromIntegral disk) disk
|
|
78 |
(fromIntegral cpu) False grpIndex
|
|
74 |
Node.create (printf "node-%02d-%03d" grpIndex idx) |
|
75 |
(fromIntegral mem) 0 mem |
|
76 |
(fromIntegral disk) disk |
|
77 |
(fromIntegral cpu) False grpIndex |
|
79 | 78 |
) [1..ncount] |
80 | 79 |
grp = Group.create (printf "group-%02d" grpIndex) |
81 | 80 |
(printf "fake-uuid-%02d" grpIndex) apol |
b/htools/Ganeti/HTools/Text.hs | ||
---|---|---|
27 | 27 |
-} |
28 | 28 |
|
29 | 29 |
module Ganeti.HTools.Text |
30 |
( |
|
31 |
loadData |
|
32 |
, parseData |
|
33 |
, loadInst |
|
34 |
, loadNode |
|
35 |
, serializeInstances |
|
36 |
, serializeNode |
|
37 |
, serializeNodes |
|
38 |
, serializeCluster |
|
39 |
) where |
|
30 |
( loadData |
|
31 |
, parseData |
|
32 |
, loadInst |
|
33 |
, loadNode |
|
34 |
, serializeInstances |
|
35 |
, serializeNode |
|
36 |
, serializeNodes |
|
37 |
, serializeCluster |
|
38 |
) where |
|
40 | 39 |
|
41 | 40 |
import Control.Monad |
42 | 41 |
import Data.List |
... | ... | |
56 | 55 |
-- | Serialize a single group. |
57 | 56 |
serializeGroup :: Group.Group -> String |
58 | 57 |
serializeGroup grp = |
59 |
printf "%s|%s|%s" (Group.name grp) (Group.uuid grp)
|
|
60 |
(allocPolicyToRaw (Group.allocPolicy grp))
|
|
58 |
printf "%s|%s|%s" (Group.name grp) (Group.uuid grp) |
|
59 |
(allocPolicyToRaw (Group.allocPolicy grp)) |
|
61 | 60 |
|
62 | 61 |
-- | Generate group file data from a group list. |
63 | 62 |
serializeGroups :: Group.List -> String |
... | ... | |
68 | 67 |
-> Node.Node -- ^ The node to be serialised |
69 | 68 |
-> String |
70 | 69 |
serializeNode gl node = |
71 |
printf "%s|%.0f|%d|%d|%.0f|%d|%.0f|%c|%s" (Node.name node)
|
|
72 |
(Node.tMem node) (Node.nMem node) (Node.fMem node)
|
|
73 |
(Node.tDsk node) (Node.fDsk node) (Node.tCpu node)
|
|
74 |
(if Node.offline node then 'Y' else 'N')
|
|
75 |
(Group.uuid grp)
|
|
70 |
printf "%s|%.0f|%d|%d|%.0f|%d|%.0f|%c|%s" (Node.name node) |
|
71 |
(Node.tMem node) (Node.nMem node) (Node.fMem node) |
|
72 |
(Node.tDsk node) (Node.fDsk node) (Node.tCpu node) |
|
73 |
(if Node.offline node then 'Y' else 'N') |
|
74 |
(Group.uuid grp) |
|
76 | 75 |
where grp = Container.find (Node.group node) gl |
77 | 76 |
|
78 | 77 |
-- | Generate node file data from node objects. |
... | ... | |
85 | 84 |
-> Instance.Instance -- ^ The instance to be serialised |
86 | 85 |
-> String |
87 | 86 |
serializeInstance nl inst = |
88 |
let |
|
89 |
iname = Instance.name inst |
|
90 |
pnode = Container.nameOf nl (Instance.pNode inst) |
|
91 |
sidx = Instance.sNode inst |
|
92 |
snode = (if sidx == Node.noSecondary |
|
93 |
then "" |
|
94 |
else Container.nameOf nl sidx) |
|
95 |
in |
|
96 |
printf "%s|%d|%d|%d|%s|%s|%s|%s|%s|%s" |
|
97 |
iname (Instance.mem inst) (Instance.dsk inst) |
|
98 |
(Instance.vcpus inst) (instanceStatusToRaw (Instance.runSt inst)) |
|
99 |
(if Instance.autoBalance inst then "Y" else "N") |
|
100 |
pnode snode (diskTemplateToRaw (Instance.diskTemplate inst)) |
|
101 |
(intercalate "," (Instance.tags inst)) |
|
87 |
let iname = Instance.name inst |
|
88 |
pnode = Container.nameOf nl (Instance.pNode inst) |
|
89 |
sidx = Instance.sNode inst |
|
90 |
snode = (if sidx == Node.noSecondary |
|
91 |
then "" |
|
92 |
else Container.nameOf nl sidx) |
|
93 |
in printf "%s|%d|%d|%d|%s|%s|%s|%s|%s|%s" |
|
94 |
iname (Instance.mem inst) (Instance.dsk inst) |
|
95 |
(Instance.vcpus inst) (instanceStatusToRaw (Instance.runSt inst)) |
|
96 |
(if Instance.autoBalance inst then "Y" else "N") |
|
97 |
pnode snode (diskTemplateToRaw (Instance.diskTemplate inst)) |
|
98 |
(intercalate "," (Instance.tags inst)) |
|
102 | 99 |
|
103 | 100 |
-- | Generate instance file data from instance objects. |
104 | 101 |
serializeInstances :: Node.List -> Instance.List -> String |
105 | 102 |
serializeInstances nl = |
106 |
unlines . map (serializeInstance nl) . Container.elems
|
|
103 |
unlines . map (serializeInstance nl) . Container.elems |
|
107 | 104 |
|
108 | 105 |
-- | Generate complete cluster data from node and instance lists. |
109 | 106 |
serializeCluster :: ClusterData -> String |
b/htools/Ganeti/HTools/Types.hs | ||
---|---|---|
26 | 26 |
-} |
27 | 27 |
|
28 | 28 |
module Ganeti.HTools.Types |
29 |
( Idx
|
|
30 |
, Ndx
|
|
31 |
, Gdx
|
|
32 |
, NameAssoc
|
|
33 |
, Score
|
|
34 |
, Weight
|
|
35 |
, GroupID
|
|
36 |
, AllocPolicy(..)
|
|
37 |
, allocPolicyFromRaw
|
|
38 |
, allocPolicyToRaw
|
|
39 |
, InstanceStatus(..)
|
|
40 |
, instanceStatusFromRaw
|
|
41 |
, instanceStatusToRaw
|
|
42 |
, RSpec(..)
|
|
43 |
, DynUtil(..)
|
|
44 |
, zeroUtil
|
|
45 |
, baseUtil
|
|
46 |
, addUtil
|
|
47 |
, subUtil
|
|
48 |
, defVcpuRatio
|
|
49 |
, defReservedDiskRatio
|
|
50 |
, unitMem
|
|
51 |
, unitCpu
|
|
52 |
, unitDsk
|
|
53 |
, unknownField
|
|
54 |
, Placement
|
|
55 |
, IMove(..)
|
|
56 |
, DiskTemplate(..)
|
|
57 |
, diskTemplateToRaw
|
|
58 |
, diskTemplateFromRaw
|
|
59 |
, MoveJob
|
|
60 |
, JobSet
|
|
61 |
, Result(..)
|
|
62 |
, isOk
|
|
63 |
, isBad
|
|
64 |
, eitherToResult
|
|
65 |
, Element(..)
|
|
66 |
, FailMode(..)
|
|
67 |
, FailStats
|
|
68 |
, OpResult(..)
|
|
69 |
, opToResult
|
|
70 |
, connTimeout
|
|
71 |
, queryTimeout
|
|
72 |
, EvacMode(..)
|
|
73 |
) where
|
|
29 |
( Idx |
|
30 |
, Ndx |
|
31 |
, Gdx |
|
32 |
, NameAssoc |
|
33 |
, Score |
|
34 |
, Weight |
|
35 |
, GroupID |
|
36 |
, AllocPolicy(..) |
|
37 |
, allocPolicyFromRaw |
|
38 |
, allocPolicyToRaw |
|
39 |
, InstanceStatus(..) |
|
40 |
, instanceStatusFromRaw |
|
41 |
, instanceStatusToRaw |
|
42 |
, RSpec(..) |
|
43 |
, DynUtil(..) |
|
44 |
, zeroUtil |
|
45 |
, baseUtil |
|
46 |
, addUtil |
|
47 |
, subUtil |
|
48 |
, defVcpuRatio |
|
49 |
, defReservedDiskRatio |
|
50 |
, unitMem |
|
51 |
, unitCpu |
|
52 |
, unitDsk |
|
53 |
, unknownField |
|
54 |
, Placement |
|
55 |
, IMove(..) |
|
56 |
, DiskTemplate(..) |
|
57 |
, diskTemplateToRaw |
|
58 |
, diskTemplateFromRaw |
|
59 |
, MoveJob |
|
60 |
, JobSet |
|
61 |
, Result(..) |
|
62 |
, isOk |
|
63 |
, isBad |
|
64 |
, eitherToResult |
|
65 |
, Element(..) |
|
66 |
, FailMode(..) |
|
67 |
, FailStats |
|
68 |
, OpResult(..) |
|
69 |
, opToResult |
|
70 |
, connTimeout |
|
71 |
, queryTimeout |
|
72 |
, EvacMode(..) |
|
73 |
) where |
|
74 | 74 |
|
75 | 75 |
import Control.Monad |
76 | 76 |
import qualified Data.Map as M |
... | ... | |
107 | 107 |
-- changing this data type be careful about the interaction with the |
108 | 108 |
-- desired sorting order. |
109 | 109 |
$(THH.declareSADT "AllocPolicy" |
110 |
[ ("AllocPreferred", 'C.allocPolicyPreferred)
|
|
111 |
, ("AllocLastResort", 'C.allocPolicyLastResort)
|
|
112 |
, ("AllocUnallocable", 'C.allocPolicyUnallocable)
|
|
113 |
])
|
|
110 |
[ ("AllocPreferred", 'C.allocPolicyPreferred) |
|
111 |
, ("AllocLastResort", 'C.allocPolicyLastResort) |
|
112 |
, ("AllocUnallocable", 'C.allocPolicyUnallocable) |
|
113 |
]) |
|
114 | 114 |
$(THH.makeJSONInstance ''AllocPolicy) |
115 | 115 |
|
116 | 116 |
-- | The Instance real state type. |
117 | 117 |
$(THH.declareSADT "InstanceStatus" |
118 |
[ ("AdminDown", 'C.inststAdmindown)
|
|
119 |
, ("AdminOffline", 'C.inststAdminoffline)
|
|
120 |
, ("ErrorDown", 'C.inststErrordown)
|
|
121 |
, ("ErrorUp", 'C.inststErrorup)
|
|
122 |
, ("NodeDown", 'C.inststNodedown)
|
|
123 |
, ("NodeOffline", 'C.inststNodeoffline)
|
|
124 |
, ("Running", 'C.inststRunning)
|
|
125 |
, ("WrongNode", 'C.inststWrongnode)
|
|
126 |
])
|
|
118 |
[ ("AdminDown", 'C.inststAdmindown) |
|
119 |
, ("AdminOffline", 'C.inststAdminoffline) |
|
120 |
, ("ErrorDown", 'C.inststErrordown) |
|
121 |
, ("ErrorUp", 'C.inststErrorup) |
|
122 |
, ("NodeDown", 'C.inststNodedown) |
|
123 |
, ("NodeOffline", 'C.inststNodeoffline) |
|
124 |
, ("Running", 'C.inststRunning) |
|
125 |
, ("WrongNode", 'C.inststWrongnode) |
|
126 |
]) |
|
127 | 127 |
$(THH.makeJSONInstance ''InstanceStatus) |
128 | 128 |
|
129 | 129 |
-- | The resource spec type. |
130 | 130 |
data RSpec = RSpec |
131 |
{ rspecCpu :: Int -- ^ Requested VCPUs
|
|
132 |
, rspecMem :: Int -- ^ Requested memory
|
|
133 |
, rspecDsk :: Int -- ^ Requested disk
|
|
134 |
} deriving (Show, Read, Eq)
|
|
131 |
{ rspecCpu :: Int -- ^ Requested VCPUs |
|
132 |
, rspecMem :: Int -- ^ Requested memory |
|
133 |
, rspecDsk :: Int -- ^ Requested disk |
|
134 |
} deriving (Show, Read, Eq) |
|
135 | 135 |
|
136 | 136 |
-- | The dynamic resource specs of a machine (i.e. load or load |
137 | 137 |
-- capacity, as opposed to size). |
138 | 138 |
data DynUtil = DynUtil |
139 |
{ cpuWeight :: Weight -- ^ Standardised CPU usage
|
|
140 |
, memWeight :: Weight -- ^ Standardised memory load
|
|
141 |
, dskWeight :: Weight -- ^ Standardised disk I\/O usage
|
|
142 |
, netWeight :: Weight -- ^ Standardised network usage
|
|
143 |
} deriving (Show, Read, Eq)
|
|
139 |
{ cpuWeight :: Weight -- ^ Standardised CPU usage |
|
140 |
, memWeight :: Weight -- ^ Standardised memory load |
|
141 |
, dskWeight :: Weight -- ^ Standardised disk I\/O usage |
|
142 |
, netWeight :: Weight -- ^ Standardised network usage |
|
143 |
} deriving (Show, Read, Eq) |
|
144 | 144 |
|
145 | 145 |
-- | Initial empty utilisation. |
146 | 146 |
zeroUtil :: DynUtil |
... | ... | |
156 | 156 |
-- | Sum two utilisation records. |
157 | 157 |
addUtil :: DynUtil -> DynUtil -> DynUtil |
158 | 158 |
addUtil (DynUtil a1 a2 a3 a4) (DynUtil b1 b2 b3 b4) = |
159 |
DynUtil (a1+b1) (a2+b2) (a3+b3) (a4+b4)
|
|
159 |
DynUtil (a1+b1) (a2+b2) (a3+b3) (a4+b4) |
|
160 | 160 |
|
161 | 161 |
-- | Substracts one utilisation record from another. |
162 | 162 |
subUtil :: DynUtil -> DynUtil -> DynUtil |
163 | 163 |
subUtil (DynUtil a1 a2 a3 a4) (DynUtil b1 b2 b3 b4) = |
164 |
DynUtil (a1-b1) (a2-b2) (a3-b3) (a4-b4)
|
|
164 |
DynUtil (a1-b1) (a2-b2) (a3-b3) (a4-b4) |
|
165 | 165 |
|
166 | 166 |
-- | The description of an instance placement. It contains the |
167 | 167 |
-- instance index, the new primary and secondary node, the move being |
... | ... | |
178 | 178 |
|
179 | 179 |
-- | Instance disk template type. |
180 | 180 |
$(THH.declareSADT "DiskTemplate" |
181 |
[ ("DTDiskless", 'C.dtDiskless) |
|
182 |
, ("DTFile", 'C.dtFile) |
|
183 |
, ("DTSharedFile", 'C.dtSharedFile) |
|
184 |
, ("DTPlain", 'C.dtPlain) |
|
185 |
, ("DTBlock", 'C.dtBlock) |
|
186 |
, ("DTDrbd8", 'C.dtDrbd8) |
|
187 |
]) |
|
181 |
[ ("DTDiskless", 'C.dtDiskless)
|
|
182 |
, ("DTFile", 'C.dtFile)
|
|
183 |
, ("DTSharedFile", 'C.dtSharedFile)
|
|
184 |
, ("DTPlain", 'C.dtPlain)
|
|
185 |
, ("DTBlock", 'C.dtBlock)
|
|
186 |
, ("DTDrbd8", 'C.dtDrbd8)
|
|
187 |
])
|
|
188 | 188 |
$(THH.makeJSONInstance ''DiskTemplate) |
189 | 189 |
|
190 | 190 |
-- | Formatted solution output for one move (involved nodes and |
... | ... | |
237 | 237 |
deriving (Show, Read, Eq) |
238 | 238 |
|
239 | 239 |
instance Monad Result where |
240 |
(>>=) (Bad x) _ = Bad x
|
|
241 |
(>>=) (Ok x) fn = fn x
|
|
242 |
return = Ok
|
|
243 |
fail = Bad
|
|
240 |
(>>=) (Bad x) _ = Bad x |
|
241 |
(>>=) (Ok x) fn = fn x |
|
242 |
return = Ok |
|
243 |
fail = Bad |
|
244 | 244 |
|
245 | 245 |
instance MonadPlus Result where |
246 |
mzero = Bad "zero Result when used as MonadPlus"
|
|
247 |
-- for mplus, when we 'add' two Bad values, we concatenate their
|
|
248 |
-- error descriptions
|
|
249 |
(Bad x) `mplus` (Bad y) = Bad (x ++ "; " ++ y)
|
|
250 |
(Bad _) `mplus` x = x
|
|
251 |
x@(Ok _) `mplus` _ = x
|
|
246 |
mzero = Bad "zero Result when used as MonadPlus" |
|
247 |
-- for mplus, when we 'add' two Bad values, we concatenate their |
|
248 |
-- error descriptions |
|
249 |
(Bad x) `mplus` (Bad y) = Bad (x ++ "; " ++ y) |
|
250 |
(Bad _) `mplus` x = x |
|
251 |
x@(Ok _) `mplus` _ = x |
|
252 | 252 |
|
253 | 253 |
-- | Simple checker for whether a 'Result' is OK. |
254 | 254 |
isOk :: Result a -> Bool |
... | ... | |
287 | 287 |
deriving (Show, Read) |
288 | 288 |
|
289 | 289 |
instance Monad OpResult where |
290 |
(OpGood x) >>= fn = fn x
|
|
291 |
(OpFail y) >>= _ = OpFail y
|
|
292 |
return = OpGood
|
|
290 |
(OpGood x) >>= fn = fn x |
|
291 |
(OpFail y) >>= _ = OpFail y |
|
292 |
return = OpGood |
|
293 | 293 |
|
294 | 294 |
-- | Conversion from 'OpResult' to 'Result'. |
295 | 295 |
opToResult :: OpResult a -> Result a |
... | ... | |
298 | 298 |
|
299 | 299 |
-- | A generic class for items that have updateable names and indices. |
300 | 300 |
class Element a where |
301 |
-- | Returns the name of the element |
|
302 |
nameOf :: a -> String |
|
303 |
-- | Returns all the known names of the element |
|
304 |
allNames :: a -> [String] |
|
305 |
-- | Returns the index of the element |
|
306 |
idxOf :: a -> Int |
|
307 |
-- | Updates the alias of the element |
|
308 |
setAlias :: a -> String -> a |
|
309 |
-- | Compute the alias by stripping a given suffix (domain) from |
|
310 |
-- the name |
|
311 |
computeAlias :: String -> a -> a |
|
312 |
computeAlias dom e = setAlias e alias |
|
313 |
where alias = take (length name - length dom) name |
|
314 |
name = nameOf e |
|
315 |
-- | Updates the index of the element |
|
316 |
setIdx :: a -> Int -> a |
|
301 |
-- | Returns the name of the element |
|
302 |
nameOf :: a -> String |
|
303 |
-- | Returns all the known names of the element |
|
304 |
allNames :: a -> [String] |
|
305 |
-- | Returns the index of the element |
|
306 |
idxOf :: a -> Int |
|
307 |
-- | Updates the alias of the element |
|
308 |
setAlias :: a -> String -> a |
|
309 |
-- | Compute the alias by stripping a given suffix (domain) from |
|
310 |
-- the name |
|
311 |
computeAlias :: String -> a -> a |
|
312 |
computeAlias dom e = setAlias e alias |
|
313 |
where alias = take (length name - length dom) name |
|
314 |
name = nameOf e |
Also available in: Unified diff