diff --git a/Ganeti/HTools/Cluster.hs b/Ganeti/HTools/Cluster.hs
index f5a956b7d28b2e0bf1e9bce3b80ce70f5b473985..19a0fb0c58d431ea23b9864dc526591238a65205 100644
--- a/Ganeti/HTools/Cluster.hs
+++ b/Ganeti/HTools/Cluster.hs
@@ -61,6 +61,8 @@ module Ganeti.HTools.Cluster
-- * Allocation functions
, iterateAlloc
, tieredAlloc
+ , instanceGroup
+ , findSplitInstances
) where
import Data.List
@@ -830,3 +832,27 @@ iMoveToJob nl il idx move =
ReplaceSecondary ns -> [ opR ns ]
ReplaceAndFailover np -> [ opR np, opF ]
FailoverAndReplace ns -> [ opF, opR ns ]
+
+-- | Computes the group of an instance
+instanceGroup :: Node.List -> Instance.Instance -> Result GroupID
+instanceGroup nl i =
+ let sidx = Instance.sNode i
+ pnode = Container.find (Instance.pNode i) nl
+ snode = if sidx == Node.noSecondary
+ then pnode
+ else Container.find sidx nl
+ puuid = Node.group pnode
+ suuid = Node.group snode
+ in if puuid /= suuid
+ then fail ("Instance placed accross two node groups, primary " ++ puuid ++
+ ", secondary " ++ suuid)
+ else return puuid
+
+-- | Compute the list of badly allocated instances (split across node
+-- groups)
+findSplitInstances :: Node.List -> Instance.List -> [Instance.Instance]
+findSplitInstances nl il =
+ filter (not . isOk . instanceGroup nl) (Container.elems il)
+ where isOk x = case x of
+ Bad _ -> False
+ _ -> True
diff --git a/Ganeti/HTools/QC.hs b/Ganeti/HTools/QC.hs
index cd3ae64ea934afcf86e41a5be91f70d0807d9b53..818827d5cbe849b01e50aaa494d9a7d878297c24 100644
--- a/Ganeti/HTools/QC.hs
+++ b/Ganeti/HTools/QC.hs
@@ -704,6 +704,22 @@ prop_ClusterAllocBalance node =
tbl = Cluster.Table ynl il' cv []
in canBalance tbl True False
+-- | Checks consistency
+prop_ClusterCheckConsistency node inst =
+ let nl = makeSmallCluster node 3
+ [node1, node2, node3] = Container.elems nl
+ node3' = node3 { Node.group = "other-uuid" }
+ nl' = Container.add (Node.idx node3') node3' nl
+ inst1 = Instance.setBoth inst (Node.idx node1) (Node.idx node2)
+ inst2 = Instance.setBoth inst (Node.idx node1) Node.noSecondary
+ inst3 = Instance.setBoth inst (Node.idx node1) (Node.idx node3)
+ ccheck = Cluster.findSplitInstances nl' . Container.fromAssocList
+ in null (ccheck [(0, inst1)]) &&
+ null (ccheck [(0, inst2)]) &&
+ (not . null $ ccheck [(0, inst3)])
+
+
+
testCluster =
[ run prop_Score_Zero
, run prop_CStats_sane
@@ -711,6 +727,7 @@ testCluster =
, run prop_ClusterCanTieredAlloc
, run prop_ClusterAllocEvac
, run prop_ClusterAllocBalance
+ , run prop_ClusterCheckConsistency
]
-- | Check that opcode serialization is idempotent