Rework the loader model
[ganeti-local] / Ganeti / HTools / Node.hs
1 {-| Module describing a node.
2
3     All updates are functional (copy-based) and return a new node with
4     updated value.
5 -}
6
7 module Ganeti.HTools.Node
8     (
9       Node(failN1, idx, t_mem, n_mem, f_mem, t_dsk, f_dsk,
10            p_mem, p_dsk, p_rem,
11            plist, slist, offline)
12     -- * Constructor
13     , create
14     -- ** Finalization after data loading
15     , buildPeers
16     , setIdx
17     , setOffline
18     , setXmem
19     , setFmem
20     -- * Instance (re)location
21     , removePri
22     , removeSec
23     , addPri
24     , addSec
25     , setPri
26     , setSec
27     -- * Formatting
28     , list
29     -- * Misc stuff
30     , AssocList
31     , noSecondary
32     ) where
33
34 import Data.List
35 import Text.Printf (printf)
36
37 import qualified Ganeti.HTools.Container as Container
38 import qualified Ganeti.HTools.Instance as Instance
39 import qualified Ganeti.HTools.PeerMap as PeerMap
40
41 import Ganeti.HTools.Utils
42
43 data Node = Node { t_mem :: Double -- ^ total memory (MiB)
44                  , n_mem :: Int    -- ^ node memory (MiB)
45                  , f_mem :: Int    -- ^ free memory (MiB)
46                  , x_mem :: Int    -- ^ unaccounted memory (MiB)
47                  , t_dsk :: Double -- ^ total disk space (MiB)
48                  , f_dsk :: Int    -- ^ free disk space (MiB)
49                  , plist :: [Int]  -- ^ list of primary instance indices
50                  , slist :: [Int]  -- ^ list of secondary instance indices
51                  , idx :: Int      -- ^ internal index for book-keeping
52                  , peers :: PeerMap.PeerMap -- ^ pnode to instance mapping
53                  , failN1:: Bool   -- ^ whether the node has failed n1
54                  , r_mem :: Int    -- ^ maximum memory needed for
55                                    -- failover by primaries of this node
56                  , p_mem :: Double -- ^ percent of free memory
57                  , p_dsk :: Double -- ^ percent of free disk
58                  , p_rem :: Double -- ^ percent of reserved memory
59                  , offline :: Bool -- ^ whether the node should not be used
60                                    -- for allocations and skipped from
61                                    -- score computations
62   } deriving (Show)
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
71 {- | Create a new node.
72
73 The index and the peers maps are empty, and will be need to be update
74 later via the 'setIdx' and 'buildPeers' functions.
75
76 -}
77 create :: Double -> Int -> Int -> Double -> Int -> Bool -> Node
78 create mem_t_init mem_n_init mem_f_init dsk_t_init dsk_f_init
79        offline_init =
80     Node
81     {
82       t_mem = mem_t_init,
83       n_mem = mem_n_init,
84       f_mem = mem_f_init,
85       t_dsk = dsk_t_init,
86       f_dsk = dsk_f_init,
87       plist = [],
88       slist = [],
89       failN1 = True,
90       idx = -1,
91       peers = PeerMap.empty,
92       r_mem = 0,
93       p_mem = (fromIntegral mem_f_init) / mem_t_init,
94       p_dsk = (fromIntegral dsk_f_init) / dsk_t_init,
95       p_rem = 0,
96       offline = offline_init,
97       x_mem = 0
98     }
99
100 -- | Changes the index.
101 -- This is used only during the building of the data structures.
102 setIdx :: Node -> Int -> Node
103 setIdx t i = t {idx = i}
104
105 -- | Sets the offline attribute
106 setOffline :: Node -> Bool -> Node
107 setOffline t val = t { offline = val }
108
109 -- | Sets the unnaccounted memory
110 setXmem :: Node -> Int -> Node
111 setXmem t val = t { x_mem = val }
112
113 -- | Sets the free memory
114 setFmem :: Node -> Int -> Node
115 setFmem t new_mem =
116     let new_n1 = computeFailN1 (r_mem t) new_mem (f_dsk t)
117         new_mp = (fromIntegral new_mem) / (t_mem t)
118     in
119       t { f_mem = new_mem, failN1 = new_n1, p_mem = new_mp }
120
121 -- | Given the rmem, free memory and disk, computes the failn1 status.
122 computeFailN1 :: Int -> Int -> Int -> Bool
123 computeFailN1 new_rmem new_mem new_dsk =
124     new_mem <= new_rmem || new_dsk <= 0
125
126 -- | Given the new free memory and disk, fail if any of them is below zero.
127 failHealth :: Int -> Int -> Bool
128 failHealth new_mem new_dsk = new_mem <= 0 || new_dsk <= 0
129
130 -- | Computes the maximum reserved memory for peers from a peer map.
131 computeMaxRes :: PeerMap.PeerMap -> PeerMap.Elem
132 computeMaxRes new_peers = PeerMap.maxElem new_peers
133
134 -- | Builds the peer map for a given node.
135 buildPeers :: Node -> Container.Container Instance.Instance -> Int -> Node
136 buildPeers t il num_nodes =
137     let mdata = map
138                 (\i_idx -> let inst = Container.find i_idx il
139                            in (Instance.pnode inst, Instance.mem inst))
140                 (slist t)
141         pmap = PeerMap.accumArray (+) 0 (0, num_nodes - 1) mdata
142         new_rmem = computeMaxRes pmap
143         new_failN1 = computeFailN1 new_rmem (f_mem t) (f_dsk t)
144         new_prem = (fromIntegral new_rmem) / (t_mem t)
145     in t {peers=pmap, failN1 = new_failN1, r_mem = new_rmem, p_rem = new_prem}
146
147 -- | Removes a primary instance.
148 removePri :: Node -> Instance.Instance -> Node
149 removePri t inst =
150     let iname = Instance.idx inst
151         new_plist = delete iname (plist t)
152         new_mem = f_mem t + Instance.mem inst
153         new_dsk = f_dsk t + Instance.dsk inst
154         new_mp = (fromIntegral new_mem) / (t_mem t)
155         new_dp = (fromIntegral new_dsk) / (t_dsk t)
156         new_failn1 = computeFailN1 (r_mem t) new_mem new_dsk
157     in t {plist = new_plist, f_mem = new_mem, f_dsk = new_dsk,
158           failN1 = new_failn1, p_mem = new_mp, p_dsk = new_dp}
159
160 -- | Removes a secondary instance.
161 removeSec :: Node -> Instance.Instance -> Node
162 removeSec t inst =
163     let iname = Instance.idx inst
164         pnode = Instance.pnode inst
165         new_slist = delete iname (slist t)
166         new_dsk = f_dsk t + Instance.dsk inst
167         old_peers = peers t
168         old_peem = PeerMap.find pnode old_peers
169         new_peem =  old_peem - (Instance.mem inst)
170         new_peers = PeerMap.add pnode new_peem old_peers
171         old_rmem = r_mem t
172         new_rmem = if old_peem < old_rmem then
173                        old_rmem
174                    else
175                        computeMaxRes new_peers
176         new_prem = (fromIntegral new_rmem) / (t_mem t)
177         new_failn1 = computeFailN1 new_rmem (f_mem t) new_dsk
178         new_dp = (fromIntegral new_dsk) / (t_dsk t)
179     in t {slist = new_slist, f_dsk = new_dsk, peers = new_peers,
180           failN1 = new_failn1, r_mem = new_rmem, p_dsk = new_dp,
181           p_rem = new_prem}
182
183 -- | Adds a primary instance.
184 addPri :: Node -> Instance.Instance -> Maybe Node
185 addPri t inst =
186     let iname = Instance.idx inst
187         new_mem = f_mem t - Instance.mem inst
188         new_dsk = f_dsk t - Instance.dsk inst
189         new_failn1 = computeFailN1 (r_mem t) new_mem new_dsk in
190       if (failHealth new_mem new_dsk) || (new_failn1 && not (failN1 t)) then
191         Nothing
192       else
193         let new_plist = iname:(plist t)
194             new_mp = (fromIntegral new_mem) / (t_mem t)
195             new_dp = (fromIntegral new_dsk) / (t_dsk t)
196         in
197         Just t {plist = new_plist, f_mem = new_mem, f_dsk = new_dsk,
198                 failN1 = new_failn1, p_mem = new_mp, p_dsk = new_dp}
199
200 -- | Adds a secondary instance.
201 addSec :: Node -> Instance.Instance -> Int -> Maybe Node
202 addSec t inst pdx =
203     let iname = Instance.idx inst
204         old_peers = peers t
205         old_mem = f_mem t
206         new_dsk = f_dsk t - Instance.dsk inst
207         new_peem = PeerMap.find pdx old_peers + Instance.mem inst
208         new_peers = PeerMap.add pdx new_peem old_peers
209         new_rmem = max (r_mem t) new_peem
210         new_prem = (fromIntegral new_rmem) / (t_mem t)
211         new_failn1 = computeFailN1 new_rmem old_mem new_dsk in
212     if (failHealth old_mem new_dsk) || (new_failn1 && not (failN1 t)) then
213         Nothing
214     else
215         let new_slist = iname:(slist t)
216             new_dp = (fromIntegral new_dsk) / (t_dsk t)
217         in
218         Just t {slist = new_slist, f_dsk = new_dsk,
219                 peers = new_peers, failN1 = new_failn1,
220                 r_mem = new_rmem, p_dsk = new_dp,
221                 p_rem = new_prem}
222
223 -- | Add a primary instance to a node without other updates
224 setPri :: Node -> Int -> Node
225 setPri t idx = t { plist = idx:(plist t) }
226
227 -- | Add a secondary instance to a node without other updates
228 setSec :: Node -> Int -> Node
229 setSec t idx = t { slist = idx:(slist t) }
230
231 -- | String converter for the node list functionality.
232 list :: Int -> String -> Node -> String
233 list mname n t =
234     let pl = plist t
235         sl = slist t
236         mp = p_mem t
237         dp = p_dsk t
238         off = offline t
239         fn = failN1 t
240         tmem = t_mem t
241         nmem = n_mem t
242         xmem = x_mem t
243         fmem = f_mem t
244         imem = (truncate tmem) - nmem - xmem - fmem
245     in
246       printf " %c %-*s %5.0f %5d %5d %5d %5d %5d %5.0f %5d %3d %3d %.5f %.5f"
247                  (if off then '-' else if fn then '*' else ' ')
248                  mname n tmem nmem imem xmem fmem (r_mem t)
249                  ((t_dsk t) / 1024) ((f_dsk t) `div` 1024)
250                  (length pl) (length sl)
251                  mp dp