Currently, the part that loads/saves locks is generic, with no lock
specifics. Abstract it so that it can be reused for temporary
reservations.

Signed-off-by: Petr Pudlak <[email protected]>
---
 Makefile.am                         |   1 +
 src/Ganeti/Locking/Locks.hs         |  41 +------------
 src/Ganeti/WConfd/DeathDetection.hs |   4 +-
 src/Ganeti/WConfd/Persistent.hs     | 114 ++++++++++++++++++++++++++++++++++++
 src/Ganeti/WConfd/Server.hs         |  18 ++----
 5 files changed, 123 insertions(+), 55 deletions(-)
 create mode 100644 src/Ganeti/WConfd/Persistent.hs

diff --git a/Makefile.am b/Makefile.am
index 1cfca43..4aab1cf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -880,6 +880,7 @@ HS_LIB_SRCS = \
        src/Ganeti/WConfd/DeathDetection.hs \
        src/Ganeti/WConfd/Language.hs \
        src/Ganeti/WConfd/Monad.hs \
+       src/Ganeti/WConfd/Persistent.hs \
        src/Ganeti/WConfd/Server.hs \
        src/Ganeti/WConfd/Ssconf.hs \
        src/Ganeti/WConfd/TempRes.hs
diff --git a/src/Ganeti/Locking/Locks.hs b/src/Ganeti/Locking/Locks.hs
index c67f068..9ab51c7 100644
--- a/src/Ganeti/Locking/Locks.hs
+++ b/src/Ganeti/Locking/Locks.hs
@@ -31,30 +31,20 @@ module Ganeti.Locking.Locks
   , ClientType(..)
   , ClientId(..)
   , GanetiLockWaiting
-  , loadLockAllocation
-  , writeLocksAsyncTask
   , LockLevel(..)
   , lockLevel
   ) where
 
 import Control.Applicative ((<$>), (<*>), pure)
 import Control.Monad ((>=>), liftM)
-import Control.Monad.Base (MonadBase, liftBase)
-import Control.Monad.Error (MonadError, catchError)
 import Data.List (stripPrefix)
 import System.Posix.Types (ProcessID)
 import qualified Text.JSON as J
 
-
-import Ganeti.BasicTypes
-import Ganeti.Errors (ResultG, GanetiException)
-import Ganeti.JSON (readEitherString, fromJResultE)
+import Ganeti.JSON (readEitherString)
 import Ganeti.Locking.Types
 import Ganeti.Locking.Waiting
-import Ganeti.Logging.Lifted (MonadLog, logDebug, logEmergency)
 import Ganeti.Types
-import Ganeti.Utils.Atomic
-import Ganeti.Utils.AsyncWorker
 
 -- | The type of Locks available in Ganeti. The order of this type
 -- is the lock oder.
@@ -231,32 +221,3 @@ instance J.JSON ClientId where
 -- | The type of lock Allocations in Ganeti. In Ganeti, the owner of
 -- locks are jobs.
 type GanetiLockWaiting = LockWaiting GanetiLocks ClientId Integer
-
--- | Load a lock allocation from disk.
-loadLockAllocation :: FilePath -> ResultG GanetiLockWaiting
-loadLockAllocation =
-  liftIO . readFile
-  >=> fromJResultE "parsing lock waiting structure" . J.decodeStrict
-
--- | Write lock allocation to disk, overwriting any previous lock
--- allocation stored there.
-writeLocks :: (MonadBase IO m, MonadError GanetiException m, MonadLog m)
-           => FilePath -> GanetiLockWaiting -> m ()
-writeLocks fpath lockWait = do
-  logDebug "Async. lock status writer: Starting write"
-  toErrorBase . liftIO . atomicWriteFile fpath $ J.encode lockWait
-  logDebug "Async. lock status writer: written"
-
--- | Construct an asynchronous worker whose action is to save the
--- current state of the lock allocation.
--- The worker's action reads the lock allocation using the given @IO@
--- action. Any inbetween changes to the file are tacitly ignored.
-writeLocksAsyncTask :: FilePath -- ^ Path to the lock file
-                    -> IO GanetiLockWaiting -- ^ An action to read the
-                                            -- current lock allocation
-                    -> ResultG (AsyncWorker () ())
-writeLocksAsyncTask fpath lockWaitingAction = mkAsyncWorker_ $
-  catchError (do
-    locks <- liftBase lockWaitingAction
-    writeLocks fpath locks
-  ) (logEmergency . (++) "Can't write lock allocation status: " . show)
diff --git a/src/Ganeti/WConfd/DeathDetection.hs 
b/src/Ganeti/WConfd/DeathDetection.hs
index 0c68404..0cecbe7 100644
--- a/src/Ganeti/WConfd/DeathDetection.hs
+++ b/src/Ganeti/WConfd/DeathDetection.hs
@@ -42,11 +42,11 @@ import System.Directory (removeFile)
 import Ganeti.BasicTypes
 import qualified Ganeti.Constants as C
 import qualified Ganeti.Locking.Allocation as L
-import qualified Ganeti.Locking.Waiting as LW
 import Ganeti.Locking.Locks (ClientId(..))
 import Ganeti.Logging.Lifted (logDebug, logInfo)
 import Ganeti.Utils.Livelock
 import Ganeti.WConfd.Monad
+import Ganeti.WConfd.Persistent
 
 -- | Interval to run clean-up tasks in microseconds
 cleanupInterval :: Int
@@ -63,7 +63,7 @@ cleanupLocksTask = forever . runResultT $ do
         died <- liftIO (isDead fpath)
         when died $ do
           logInfo $ show owner ++ " died, releasing locks"
-          modifyLockWaiting_ (LW.releaseResources owner)
+          persCleanup persistentLocks owner
           _ <- liftIO . E.try $ removeFile fpath
                :: WConfdMonad (Either IOError ())
           return ()
diff --git a/src/Ganeti/WConfd/Persistent.hs b/src/Ganeti/WConfd/Persistent.hs
new file mode 100644
index 0000000..1ff3ded
--- /dev/null
+++ b/src/Ganeti/WConfd/Persistent.hs
@@ -0,0 +1,114 @@
+{-# LANGUAGE MultiParamTypeClasses, TypeFamilies #-}
+
+{-| Common types and functions for persistent resources
+
+In particular:
+- locks
+- temporary reservations
+
+-}
+
+{-
+
+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.Persistent
+  ( Persistent(..)
+  , writePersistentAsyncTask
+  , readPersistent
+  , persistentLocks
+  ) where
+
+import Control.Monad.Error
+import System.Directory (doesFileExist)
+import qualified Text.JSON as J
+
+import Ganeti.BasicTypes
+import Ganeti.Errors
+import qualified Ganeti.JSON as J
+import Ganeti.Locking.Waiting (emptyWaiting)
+import Ganeti.Locking.Locks (ClientId(..), GanetiLockWaiting)
+import Ganeti.Logging
+import qualified Ganeti.Path as Path
+import Ganeti.WConfd.Core (freeLocks)
+import Ganeti.WConfd.Monad
+import Ganeti.Utils.Atomic
+import Ganeti.Utils.AsyncWorker
+
+-- * Common definitions
+
+-- ** The data type that collects all required operations
+
+-- | A collection of operations needed for persisting a resource.
+data Persistent a = Persistent
+  { persName :: String
+  , persPath :: IO FilePath
+  , persEmpty :: a
+  , persCleanup :: ClientId -> WConfdMonad ()
+  -- ^ The clean-up action needs to be a full 'WConfdMonad' action as it
+  -- might need to do some complex processing, such as notifying
+  -- clients that some locks are available.
+  }
+
+-- ** Common functions
+
+-- | Construct an asynchronous worker whose action is to save the
+-- current state of the persistent state.
+-- The worker's action reads the state using the given @IO@
+-- action. Any inbetween changes to the file are tacitly ignored.
+writePersistentAsyncTask
+  :: (J.JSON a) => Persistent a -> IO a -> ResultG (AsyncWorker () ())
+writePersistentAsyncTask pers readAction = mkAsyncWorker_ $
+  catchError (do
+    let prefix = "Async. " ++ persName pers ++ " writer: "
+    fpath <- liftIO $ persPath pers
+    logDebug $ prefix ++ "Starting write to " ++ fpath
+    state <- liftIO readAction
+    toErrorBase . liftIO . atomicWriteFile fpath . J.encode $ state
+    logDebug $ prefix ++ "written"
+  ) (logEmergency . (++) ("Can't write " ++ persName pers ++ " state: ")
+                  . show)
+
+-- | Load a persistent data structure from disk.
+readPersistent :: (J.JSON a) => Persistent a -> ResultG a
+readPersistent pers = do
+  logDebug $ "Reading " ++ persName pers
+  file <- liftIO $ persPath pers
+  file_present <- liftIO $ doesFileExist file
+  if file_present
+    then
+      liftIO (persPath pers >>= readFile)
+        >>= J.fromJResultE ("parsing " ++ persName pers) . J.decodeStrict
+    else do
+      logInfo $ "Note: No saved data for " ++ persName pers
+                ++ ", silently assuming empty."
+      return (persEmpty pers)
+
+-- * Implementations
+
+-- ** Locks
+
+persistentLocks :: Persistent GanetiLockWaiting
+persistentLocks = Persistent
+  { persName = "lock allocation state"
+  , persPath = Path.lockStatusFile
+  , persEmpty = emptyWaiting
+  , persCleanup = freeLocks
+  }
diff --git a/src/Ganeti/WConfd/Server.hs b/src/Ganeti/WConfd/Server.hs
index 5b89737..375e237 100644
--- a/src/Ganeti/WConfd/Server.hs
+++ b/src/Ganeti/WConfd/Server.hs
@@ -35,14 +35,11 @@ import Control.Concurrent (forkIO)
 import Control.Exception
 import Control.Monad
 import Control.Monad.Error
-import System.Directory (doesFileExist)
 
 import Ganeti.BasicTypes
 import Ganeti.Daemon
 import Ganeti.Daemon.Utils (handleMasterVerificationOptions)
-import Ganeti.Logging (logInfo, logDebug)
-import Ganeti.Locking.Locks
-import Ganeti.Locking.Waiting
+import Ganeti.Logging (logDebug)
 import qualified Ganeti.Path as Path
 import Ganeti.THH.RPC
 import Ganeti.UDSServer
@@ -50,11 +47,12 @@ import Ganeti.UDSServer
 import Ganeti.Errors (formatError)
 import Ganeti.Runtime
 import Ganeti.WConfd.ConfigState
+import Ganeti.WConfd.ConfigVerify
 import Ganeti.WConfd.ConfigWriter
 import Ganeti.WConfd.Core
 import Ganeti.WConfd.DeathDetection (cleanupLocksTask)
 import Ganeti.WConfd.Monad
-import Ganeti.WConfd.ConfigVerify
+import Ganeti.WConfd.Persistent
 
 handler :: DaemonHandle -> RpcServer WConfdMonadInt
 handler ch = $( mkRpcM exportedFunctions )
@@ -78,26 +76,20 @@ prepMain _ _ = do
   -- TODO: Lock the configuration file so that running the daemon twice fails?
   conf_file <- Path.clusterConfFile
 
-  lock_file <- Path.lockStatusFile
-  lock_file_present <- doesFileExist lock_file
-  unless lock_file_present
-    $ logInfo "No saved lock status; assuming all locks free"
   dh <- toErrorBase
         . withErrorT (strMsg . ("Initialization of the daemon failed" ++)
                              . formatError) $ do
     ents <- getEnts
     (cdata, cstat) <- loadConfigFromFile conf_file
     verifyConfigErr cdata
-    lock <- if lock_file_present
-              then loadLockAllocation lock_file
-              else return emptyWaiting
+    lock <- readPersistent persistentLocks
     mkDaemonHandle conf_file
                    (mkConfigState cdata)
                    lock
                    (saveConfigAsyncTask conf_file cstat)
                    (distMCsAsyncTask ents conf_file)
                    distSSConfAsyncTask
-                   (writeLocksAsyncTask lock_file)
+                   (writePersistentAsyncTask persistentLocks)
 
   return (s, dh)
 
-- 
2.0.0.526.g5318336

Reply via email to