module Ganeti.WConfd.Core where
import Control.Arrow ((&&&))
import Control.Concurrent (myThreadId)
import Control.Lens.Setter (set)
import Control.Monad (liftM, unless)
import qualified Data.Map as M
import qualified Data.Set as S
import Language.Haskell.TH (Name)
import System.Posix.Process (getProcessID)
import qualified System.Random as Rand
import Ganeti.BasicTypes
import qualified Ganeti.Constants as C
import qualified Ganeti.JSON as J
import qualified Ganeti.Locking.Allocation as L
import Ganeti.Logging (logDebug, logWarning)
import Ganeti.Locking.Locks ( GanetiLocks(ConfigLock, BGL)
, LockLevel(LevelConfig)
, lockLevel, LockLevel
, ClientType(ClientOther), ClientId(..) )
import qualified Ganeti.Locking.Waiting as LW
import Ganeti.Objects (ConfigData, DRBDSecret, LogicalVolume, Ip4Address)
import Ganeti.Objects.Lens (configClusterL, clusterMasterNodeL)
import Ganeti.WConfd.ConfigState (csConfigDataL)
import qualified Ganeti.WConfd.ConfigVerify as V
import Ganeti.WConfd.DeathDetection (cleanupLocks)
import Ganeti.WConfd.Language
import Ganeti.WConfd.Monad
import qualified Ganeti.WConfd.TempRes as T
import qualified Ganeti.WConfd.ConfigWriter as CW
echo :: String -> WConfdMonad String
echo = return
checkConfigLock :: ClientId -> L.OwnerState -> WConfdMonad ()
checkConfigLock cid state = do
la <- readLockAllocation
unless (L.holdsLock cid ConfigLock state la)
. failError $ "Requested lock " ++ show state
++ " on the configuration missing"
readConfig :: WConfdMonad ConfigData
readConfig = CW.readConfig
writeConfig :: ClientId -> ConfigData -> WConfdMonad ()
writeConfig ident cdata = do
checkConfigLock ident L.OwnExclusive
CW.writeConfig cdata
verifyConfig :: WConfdMonad ()
verifyConfig = CW.readConfig >>= V.verifyConfigErr
lockConfig
:: ClientId
-> Bool
-> WConfdMonad (J.MaybeForJSON ConfigData)
lockConfig cid shared = do
let (reqtype, owntype) = if shared
then (ReqShared, L.OwnShared)
else (ReqExclusive, L.OwnExclusive)
la <- readLockAllocation
if L.holdsLock cid ConfigLock owntype la
then do
logWarning $ "Client " ++ show cid ++ " asked to lock the config"
++ " while owning the lock"
liftM (J.MaybeForJSON . Just) CW.readConfig
else do
waiting <- tryUpdateLocks cid [(ConfigLock, reqtype)]
liftM J.MaybeForJSON $ case waiting of
[] -> liftM Just CW.readConfig
_ -> return Nothing
unlockConfig
:: ClientId -> WConfdMonad ()
unlockConfig cid = freeLocksLevel cid LevelConfig
flushConfig :: WConfdMonad ()
flushConfig = forceConfigStateDistribution
dropAllReservations :: ClientId -> WConfdMonad ()
dropAllReservations cid =
modifyTempResState (const $ T.dropAllReservations cid)
computeDRBDMap :: WConfdMonad T.DRBDMap
computeDRBDMap = uncurry T.computeDRBDMap =<< readTempResState
allocateDRBDMinor
:: T.InstanceUUID -> [T.NodeUUID] -> WConfdMonad [T.DRBDMinor]
allocateDRBDMinor inst nodes =
modifyTempResStateErr (\cfg -> T.allocateDRBDMinor cfg inst nodes)
releaseDRBDMinors
:: T.InstanceUUID -> WConfdMonad ()
releaseDRBDMinors inst = modifyTempResState (const $ T.releaseDRBDMinors inst)
generateMAC
:: ClientId -> J.MaybeForJSON T.NetworkUUID -> WConfdMonad T.MAC
generateMAC cid (J.MaybeForJSON netId) = do
g <- liftIO Rand.newStdGen
modifyTempResStateErr $ T.generateMAC g cid netId
reserveMAC :: ClientId -> T.MAC -> WConfdMonad ()
reserveMAC = (modifyTempResStateErr .) . T.reserveMAC
generateDRBDSecret :: ClientId -> WConfdMonad DRBDSecret
generateDRBDSecret cid = do
g <- liftIO Rand.newStdGen
modifyTempResStateErr $ T.generateDRBDSecret g cid
reserveLV :: ClientId -> LogicalVolume -> WConfdMonad ()
reserveLV jobId lv = modifyTempResStateErr $ T.reserveLV jobId lv
reserveIp :: ClientId -> T.NetworkUUID -> Ip4Address -> Bool -> WConfdMonad ()
reserveIp = (((modifyTempResStateErr .) .) .) . T.reserveIp
releaseIp :: ClientId -> T.NetworkUUID -> Ip4Address -> WConfdMonad ()
releaseIp = (((modifyTempResStateErr .) const .) .) . T.releaseIp
generateIp :: ClientId -> T.NetworkUUID -> WConfdMonad Ip4Address
generateIp = (modifyTempResStateErr .) . T.generateIp
commitTemporaryIps :: ClientId -> WConfdMonad ()
commitTemporaryIps = modifyConfigDataErr_ . T.commitReservedIps
commitReleaseTemporaryIp
:: T.NetworkUUID -> Ip4Address -> WConfdMonad ()
commitReleaseTemporaryIp net_uuid addr =
modifyConfigDataErr_ (const $ T.commitReleaseIp net_uuid addr)
listReservedIps :: ClientId -> WConfdMonad [T.IPv4Reservation]
listReservedIps jobId =
liftM (S.toList . T.listReservedIps jobId . snd) readTempResState
listLocks :: ClientId -> WConfdMonad [(GanetiLocks, L.OwnerState)]
listLocks cid = liftM (M.toList . L.listLocks cid) readLockAllocation
listAllLocks :: WConfdMonad [GanetiLocks]
listAllLocks = liftM L.listAllLocks readLockAllocation
listAllLocksOwners :: WConfdMonad [(GanetiLocks, [(ClientId, L.OwnerState)])]
listAllLocksOwners = liftM L.listAllLocksOwners readLockAllocation
listLocksWaitingStatus :: WConfdMonad
( [(GanetiLocks, [(ClientId, L.OwnerState)])]
, [(Integer, ClientId, [L.LockRequest GanetiLocks])]
)
listLocksWaitingStatus = liftM ( (L.listAllLocksOwners . LW.getAllocation)
&&& (S.toList . LW.getPendingRequests) )
readLockWaiting
tryUpdateLocks :: ClientId -> GanetiLockRequest -> WConfdMonad [ClientId]
tryUpdateLocks cid req =
liftM S.toList
. (>>= toErrorStr)
$ modifyLockWaiting (LW.updateLocks cid (fromGanetiLockRequest req))
updateLocksWaiting :: ClientId -> Integer
-> GanetiLockRequest -> WConfdMonad [ClientId]
updateLocksWaiting cid prio req =
liftM S.toList
. (>>= toErrorStr)
. modifyLockWaiting
$ LW.safeUpdateLocksWaiting prio cid (fromGanetiLockRequest req)
hasPendingRequest :: ClientId -> WConfdMonad Bool
hasPendingRequest cid = liftM (LW.hasPendingRequest cid) readLockWaiting
freeLocks :: ClientId -> WConfdMonad ()
freeLocks cid =
modifyLockWaiting_ $ LW.releaseResources cid
freeLocksLevel :: ClientId -> LockLevel -> WConfdMonad ()
freeLocksLevel cid level =
modifyLockWaiting_ $ LW.freeLocksPredicate ((==) level . lockLevel) cid
downGradeLocksLevel :: ClientId -> LockLevel -> WConfdMonad ()
downGradeLocksLevel cid level =
modifyLockWaiting_ $ LW.downGradeLocksPredicate ((==) level . lockLevel) cid
intersectLocks :: ClientId -> [GanetiLocks] -> WConfdMonad ()
intersectLocks cid locks = modifyLockWaiting_ $ LW.intersectLocks locks cid
opportunisticLockUnion :: ClientId
-> [(GanetiLocks, L.OwnerState)]
-> WConfdMonad [GanetiLocks]
opportunisticLockUnion cid req =
modifyLockWaiting $ LW.opportunisticLockUnion cid req
guardedOpportunisticLockUnion :: Int
-> ClientId
-> [(GanetiLocks, L.OwnerState)]
-> WConfdMonad [GanetiLocks]
guardedOpportunisticLockUnion count cid req =
modifyLockWaiting $ LW.guardedOpportunisticLockUnion count cid req
prepareClusterDestruction :: ClientId -> WConfdMonad ()
prepareClusterDestruction cid = do
la <- readLockAllocation
unless (L.holdsLock cid BGL L.OwnExclusive la)
. failError $ "Cluster destruction requested without owning BGL exclusively"
logDebug $ "preparing cluster destruction as requested by " ++ show cid
dh <- daemonHandle
pid <- liftIO getProcessID
tid <- liftIO myThreadId
let mycid = ClientId { ciIdentifier = ClientOther $ "wconfd-" ++ show tid
, ciLockFile = dhLivelock dh
, ciPid = pid
}
_ <- modifyLockWaiting $ LW.updateLocksWaiting
(fromIntegral C.opPrioHighest 1) mycid
[L.requestExclusive BGL]
_ <- modifyLockWaiting $ LW.updateLocks cid [L.requestRelease BGL]
modifyConfigState $ (,) ()
. set (csConfigDataL . configClusterL . clusterMasterNodeL) ""
exportedFunctions :: [Name]
exportedFunctions = [ 'echo
, 'cleanupLocks
, 'prepareClusterDestruction
, 'readConfig
, 'writeConfig
, 'verifyConfig
, 'lockConfig
, 'unlockConfig
, 'flushConfig
, 'dropAllReservations
, 'computeDRBDMap
, 'allocateDRBDMinor
, 'releaseDRBDMinors
, 'reserveMAC
, 'generateMAC
, 'generateDRBDSecret
, 'reserveLV
, 'reserveIp
, 'releaseIp
, 'generateIp
, 'commitTemporaryIps
, 'commitReleaseTemporaryIp
, 'listReservedIps
, 'listLocks
, 'listAllLocks
, 'listAllLocksOwners
, 'listLocksWaitingStatus
, 'tryUpdateLocks
, 'updateLocksWaiting
, 'freeLocks
, 'freeLocksLevel
, 'downGradeLocksLevel
, 'intersectLocks
, 'opportunisticLockUnion
, 'guardedOpportunisticLockUnion
, 'hasPendingRequest
]