This patch adds the first step, the reservation of DRBD minors.

Signed-off-by: Petr Pudlak <[email protected]>
---
 Makefile.am                  |   3 +-
 src/Ganeti/WConfd/TempRes.hs | 163 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 165 insertions(+), 1 deletion(-)
 create mode 100644 src/Ganeti/WConfd/TempRes.hs

diff --git a/Makefile.am b/Makefile.am
index c9240d6..0dacbf3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -866,7 +866,8 @@ HS_LIB_SRCS = \
        src/Ganeti/WConfd/Language.hs \
        src/Ganeti/WConfd/Monad.hs \
        src/Ganeti/WConfd/Server.hs \
-       src/Ganeti/WConfd/Ssconf.hs
+       src/Ganeti/WConfd/Ssconf.hs \
+       src/Ganeti/WConfd/TempRes.hs
 
 HS_TEST_SRCS = \
        test/hs/Test/AutoConf.hs \
diff --git a/src/Ganeti/WConfd/TempRes.hs b/src/Ganeti/WConfd/TempRes.hs
new file mode 100644
index 0000000..a6844a7
--- /dev/null
+++ b/src/Ganeti/WConfd/TempRes.hs
@@ -0,0 +1,163 @@
+{-# LANGUAGE TemplateHaskell, RankNTypes, FlexibleContexts #-}
+
+{-| Pure functions for manipulating reservations of temporary objects
+
+-}
+
+{-
+
+Copyright (C) 2014 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
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+-}
+
+module Ganeti.WConfd.TempRes
+  ( TempResState(..)
+  , emptyTempResState
+  , NodeUUID
+  , InstanceUUID
+  , DRBDMinor
+  , DRBDMap
+  , trsDRBDL
+  , computeDRBDMap
+  , computeDRBDMap'
+  , allocateDRBDMinor
+  , releaseDRBDMinors
+  ) where
+
+import Control.Lens.At
+import Control.Monad.Error
+import Control.Monad.State
+import qualified Data.Foldable as F
+import Data.Maybe
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Monoid
+import qualified Data.Set as S
+
+import Ganeti.BasicTypes
+import Ganeti.Config
+import Ganeti.Errors
+import qualified Ganeti.JSON as J
+import Ganeti.Lens
+import Ganeti.Objects
+import Ganeti.Utils
+
+-- * The main reservation state
+
+-- ** Aliases to make types more meaningful:
+
+type NodeUUID = String
+
+type InstanceUUID = String
+
+type DRBDMinor = Int
+
+-- | A map of the usage of DRBD minors
+type DRBDMap = Map NodeUUID (Map DRBDMinor InstanceUUID)
+
+-- | A map of the usage of DRBD minors with possible duplicates
+type DRBDMap' = Map NodeUUID (Map DRBDMinor [InstanceUUID])
+
+-- * The state data structure
+
+-- | The state of the temporary reservations
+data TempResState = TempResState
+  { trsDRBD :: DRBDMap
+  }
+  deriving (Eq, Show)
+
+emptyTempResState :: TempResState
+emptyTempResState = TempResState M.empty
+
+$(makeCustomLenses ''TempResState)
+
+-- ** Utility functions
+
+-- | Filter values from the nested map and remove any nested maps
+-- that become empty.
+filterNested :: (Ord a, Ord b)
+             => (c -> Bool) -> Map a (Map b c) -> Map a (Map b c)
+filterNested p = M.filter (not . M.null) . fmap (M.filter p)
+
+-- * DRBDs
+
+-- | Converts a lens that works on maybe values into a lens that works
+-- on regular ones. A missing value on the input is replaced by
+-- 'mempty'.
+-- The output is is @Just something@ iff @something /= mempty@.
+maybeLens :: (Monoid a, Monoid b, Eq b)
+          => Lens s t (Maybe a) (Maybe b) -> Lens s t a b
+maybeLens l f = l (fmap (mfilter (/= mempty) . Just) . f . fromMaybe mempty)
+
+-- * DRBD functions
+
+-- | Compute the map of used DRBD minor/nodes, including possible
+-- duplicates.
+-- An error is returned if the configuration isn't consistent
+-- (for example if a referenced disk is missing etc.).
+computeDRBDMap' :: (MonadError GanetiException m)
+                => ConfigData -> TempResState -> m DRBDMap'
+computeDRBDMap' cfg trs =
+    flip execStateT (fmap (fmap (: [])) (trsDRBD trs))
+    $ F.forM_ (configInstances cfg) addDisks
+  where
+    -- | Creates a lens for modifying the list of instances
+    nodeMinor :: NodeUUID -> DRBDMinor -> Lens' DRBDMap' [InstanceUUID]
+    nodeMinor node minor = maybeLens (at node) . maybeLens (at minor)
+    -- | Adds disks of an instance within the state monad
+    addDisks inst = do
+                      disks <- toError $ getDrbdMinorsForInstance cfg inst
+                      forM_ disks $ \(minor, node) -> nodeMinor node minor
+                                                          %= (uuidOf inst :)
+
+-- | Compute the map of used DRBD minor/nodes.
+-- Report any duplicate entries as an error.
+--
+-- Unlike 'computeDRBDMap'', includes entries for all nodes, even if empty.
+computeDRBDMap :: (MonadError GanetiException m)
+               => ConfigData -> TempResState -> m DRBDMap
+computeDRBDMap cfg trs = do
+  m <- computeDRBDMap' cfg trs
+  let dups = filterNested ((>= 2) . length) m
+  unless (M.null dups) . failError
+    $ "Duplicate DRBD ports detected: " ++ show (M.toList $ fmap M.toList dups)
+  return $ fmap (fmap head . M.filter ((== 1) . length)) m
+           `M.union` (fmap (const mempty) . J.fromContainer . configNodes $ 
cfg)
+
+-- Allocate a drbd minor.
+--
+-- The free minor will be automatically computed from the existing devices.
+-- A node can be given multiple times in order to allocate multiple minors.
+-- The result is the list of minors, in the same order as the passed nodes.
+allocateDRBDMinor :: (MonadError GanetiException m, MonadState TempResState m)
+                  => ConfigData -> InstanceUUID -> [NodeUUID]
+                  -> m [DRBDMinor]
+allocateDRBDMinor cfg inst nodes = do
+  dMap <- computeDRBDMap' cfg =<< get
+  let usedMap = fmap M.keysSet dMap
+  let alloc :: S.Set DRBDMinor -> Map DRBDMinor InstanceUUID
+            -> (DRBDMinor, Map DRBDMinor InstanceUUID)
+      alloc used m = let k = findFirst 0 (M.keysSet m `S.union` used)
+                      in (k, M.insert k inst m)
+  forM nodes $ \node -> trsDRBDL . maybeLens (at node)
+                        %%= alloc (M.findWithDefault mempty node usedMap)
+
+-- Release temporary drbd minors allocated for a given instance using
+-- 'allocateDRBDMinor'.
+releaseDRBDMinors :: (MonadState TempResState m) => InstanceUUID -> m ()
+releaseDRBDMinors inst = trsDRBDL %= filterNested (/= inst)
-- 
1.9.1.423.g4596e3a

Reply via email to