Split out Objects.hs from QC.hs
[ganeti-local] / htools / Ganeti / HTools / Loader.hs
index d7f40bf..71893e3 100644 (file)
@@ -30,8 +30,6 @@ module Ganeti.HTools.Loader
   ( mergeData
   , checkData
   , assignIndices
-  , lookupName
-  , goodLookupResult
   , lookupNode
   , lookupInstance
   , lookupGroup
@@ -40,14 +38,9 @@ module Ganeti.HTools.Loader
   , Request(..)
   , ClusterData(..)
   , emptyCluster
-  , compareNameComponent
-  , prefixMatch
-  , LookupResult(..)
-  , MatchPriority(..)
   ) where
 
 import Data.List
-import Data.Function
 import qualified Data.Map as M
 import Text.Printf (printf)
 
@@ -55,7 +48,9 @@ import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Instance as Instance
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.HTools.Group as Group
+import qualified Ganeti.HTools.Cluster as Cluster
 
+import Ganeti.BasicTypes
 import Ganeti.HTools.Types
 import Ganeti.HTools.Utils
 
@@ -74,10 +69,11 @@ request-specific fields.
 
 -}
 data RqType
-  = Allocate Instance.Instance Int -- ^ A new instance allocation
-  | Relocate Idx Int [Ndx]         -- ^ Choose a new secondary node
-  | NodeEvacuate [Idx] EvacMode    -- ^ node-evacuate mode
-  | ChangeGroup [Gdx] [Idx]        -- ^ Multi-relocate mode
+  = Allocate Instance.Instance Int           -- ^ A new instance allocation
+  | Relocate Idx Int [Ndx]                   -- ^ Choose a new secondary node
+  | NodeEvacuate [Idx] EvacMode              -- ^ node-evacuate mode
+  | ChangeGroup [Gdx] [Idx]                  -- ^ Multi-relocate mode
+  | MultiAllocate [(Instance.Instance, Int)] -- ^ Multi-allocate mode
     deriving (Show, Read)
 
 -- | A complete request, as received from Ganeti.
@@ -93,27 +89,6 @@ data ClusterData = ClusterData
   , cdIPolicy   :: IPolicy       -- ^ The cluster instance policy
   } deriving (Show, Read, Eq)
 
--- | The priority of a match in a lookup result.
-data MatchPriority = ExactMatch
-                   | MultipleMatch
-                   | PartialMatch
-                   | FailMatch
-                   deriving (Show, Read, Enum, Eq, Ord)
-
--- | The result of a name lookup in a list.
-data LookupResult = LookupResult
-  { lrMatchPriority :: MatchPriority -- ^ The result type
-  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
-  , lrContent :: String
-  } deriving (Show, Read)
-
--- | Lookup results have an absolute preference ordering.
-instance Eq LookupResult where
-  (==) = (==) `on` lrMatchPriority
-
-instance Ord LookupResult where
-  compare = compare `on` lrMatchPriority
-
 -- | An empty cluster.
 emptyCluster :: ClusterData
 emptyCluster = ClusterData Container.empty Container.empty Container.empty []
@@ -124,74 +99,19 @@ emptyCluster = ClusterData Container.empty Container.empty Container.empty []
 -- | Lookups a node into an assoc list.
 lookupNode :: (Monad m) => NameAssoc -> String -> String -> m Ndx
 lookupNode ktn inst node =
-  case M.lookup node ktn of
-    Nothing -> fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst
-    Just idx -> return idx
+  maybe (fail $ "Unknown node '" ++ node ++ "' for instance " ++ inst) return $
+    M.lookup node ktn
 
 -- | Lookups an instance into an assoc list.
 lookupInstance :: (Monad m) => NameAssoc -> String -> m Idx
 lookupInstance kti inst =
-  case M.lookup inst kti of
-    Nothing -> fail $ "Unknown instance '" ++ inst ++ "'"
-    Just idx -> return idx
+  maybe (fail $ "Unknown instance '" ++ inst ++ "'") return $ M.lookup inst kti
 
 -- | Lookups a group into an assoc list.
 lookupGroup :: (Monad m) => NameAssoc -> String -> String -> m Gdx
 lookupGroup ktg nname gname =
-  case M.lookup gname ktg of
-    Nothing -> fail $ "Unknown group '" ++ gname ++ "' for node " ++ nname
-    Just idx -> return idx
-
--- | Check for prefix matches in names.
--- Implemented in Ganeti core utils.text.MatchNameComponent
--- as the regexp r"^%s(\..*)?$" % re.escape(key)
-prefixMatch :: String  -- ^ Lookup
-            -> String  -- ^ Full name
-            -> Bool    -- ^ Whether there is a prefix match
-prefixMatch = isPrefixOf . (++ ".")
-
--- | Is the lookup priority a "good" one?
-goodMatchPriority :: MatchPriority -> Bool
-goodMatchPriority ExactMatch = True
-goodMatchPriority PartialMatch = True
-goodMatchPriority _ = False
-
--- | Is the lookup result an actual match?
-goodLookupResult :: LookupResult -> Bool
-goodLookupResult = goodMatchPriority . lrMatchPriority
-
--- | Compares a canonical name and a lookup string.
-compareNameComponent :: String        -- ^ Canonical (target) name
-                     -> String        -- ^ Partial (lookup) name
-                     -> LookupResult  -- ^ Result of the lookup
-compareNameComponent cnl lkp =
-  select (LookupResult FailMatch lkp)
-  [ (cnl == lkp          , LookupResult ExactMatch cnl)
-  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
-  ]
-
--- | Lookup a string and choose the best result.
-chooseLookupResult :: String       -- ^ Lookup key
-                   -> String       -- ^ String to compare to the lookup key
-                   -> LookupResult -- ^ Previous result
-                   -> LookupResult -- ^ New result
-chooseLookupResult lkp cstr old =
-  -- default: use class order to pick the minimum result
-  select (min new old)
-  -- special cases:
-  -- short circuit if the new result is an exact match
-  [ (lrMatchPriority new == ExactMatch, new)
-  -- if both are partial matches generate a multiple match
-  , (partial2, LookupResult MultipleMatch lkp)
-  ] where new = compareNameComponent cstr lkp
-          partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
-
--- | Find the canonical name for a lookup string in a list of names.
-lookupName :: [String]      -- ^ List of keys
-           -> String        -- ^ Lookup string
-           -> LookupResult  -- ^ Result of the lookup
-lookupName l s = foldr (chooseLookupResult s)
-                       (LookupResult FailMatch s) l
+  maybe (fail $ "Unknown group '" ++ gname ++ "' for node " ++ nname) return $
+    M.lookup gname ktg
 
 -- | Given a list of elements (and their names), assign indices to them.
 assignIndices :: (Element a) =>
@@ -241,12 +161,18 @@ updateMovable :: [String]           -- ^ Selected instances (if not empty)
               -> Instance.Instance  -- ^ Target Instance
               -> Instance.Instance  -- ^ Target Instance with updated attribute
 updateMovable selinsts exinsts inst =
-  if Instance.sNode inst == Node.noSecondary ||
-     Instance.name inst `elem` exinsts ||
+  if Instance.name inst `elem` exinsts ||
      not (null selinsts || Instance.name inst `elem` selinsts)
     then Instance.setMovable inst False
     else inst
 
+-- | Disables moves for instances with a split group.
+disableSplitMoves :: Node.List -> Instance.Instance -> Instance.Instance
+disableSplitMoves nl inst =
+  if not . isOk . Cluster.instanceGroup nl $ inst
+    then Instance.setMovable inst False
+    else inst
+
 -- | Compute the longest common suffix of a list of strings that
 -- starts with a dot.
 longestDomain :: [String] -> String
@@ -303,8 +229,9 @@ mergeData um extags selinsts exinsts cdata@(ClusterData gl nl il2 tags _) =
       nl3 = Container.map (setNodePolicy gl .
                            computeAlias common_suffix .
                            (`Node.buildPeers` il4)) nl2
+      il5 = Container.map (disableSplitMoves nl3) il4
   in if' (null lkp_unknown)
-         (Ok cdata { cdNodes = nl3, cdInstances = il4 })
+         (Ok cdata { cdNodes = nl3, cdInstances = il5 })
          (Bad $ "Unknown instance(s): " ++ show(map lrContent lkp_unknown))
 
 -- | Checks the cluster data for consistency.
@@ -341,7 +268,7 @@ nodeImem :: Node.Node -> Instance.List -> Int
 nodeImem node il =
   let rfind = flip Container.find il
       il' = map rfind $ Node.pList node
-      oil' = filter Instance.instanceNotOffline il'
+      oil' = filter Instance.notOffline il'
   in sum . map Instance.mem $ oil'