Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Locking / Locks.hs @ 9c45196b

History | View | Annotate | Download (9.3 kB)

1
{-# LANGUAGE ViewPatterns, FlexibleContexts #-}
2

    
3
{-| Ganeti lock structure
4

    
5
-}
6

    
7
{-
8

    
9
Copyright (C) 2014 Google Inc.
10

    
11
This program is free software; you can redistribute it and/or modify
12
it under the terms of the GNU General Public License as published by
13
the Free Software Foundation; either version 2 of the License, or
14
(at your option) any later version.
15

    
16
This program is distributed in the hope that it will be useful, but
17
WITHOUT ANY WARRANTY; without even the implied warranty of
18
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19
General Public License for more details.
20

    
21
You should have received a copy of the GNU General Public License
22
along with this program; if not, write to the Free Software
23
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24
02110-1301, USA.
25

    
26
-}
27

    
28
module Ganeti.Locking.Locks
29
  ( GanetiLocks(..)
30
  , ClientId(..)
31
  , GanetiLockAllocation
32
  , loadLockAllocation
33
  , writeLocksAsyncTask
34
  , LockLevel(..)
35
  , lockLevel
36
  ) where
37

    
38
import Control.Monad ((>=>))
39
import Control.Monad.Base (MonadBase, liftBase)
40
import Control.Monad.Error (MonadError, catchError)
41
import Data.List (stripPrefix)
42
import qualified Text.JSON as J
43

    
44

    
45
import Ganeti.BasicTypes
46
import Ganeti.Errors (ResultG, GanetiException)
47
import Ganeti.JSON (readEitherString, fromJResultE)
48
import Ganeti.Locking.Allocation
49
import Ganeti.Locking.Types
50
import Ganeti.Logging.Lifted (MonadLog, logDebug, logEmergency)
51
import Ganeti.Types
52
import Ganeti.Utils.Atomic
53
import Ganeti.Utils.AsyncWorker
54

    
55
-- | The type of Locks available in Ganeti. The order of this type
56
-- is the lock oder.
57
data GanetiLocks = ClusterLockSet
58
                 | BGL
59
                 | InstanceLockSet
60
                 | Instance String
61
                 | NodeAllocLockSet
62
                 | NAL
63
                 | NodeGroupLockSet
64
                 | NodeGroup String
65
                 | NodeLockSet
66
                 | Node String
67
                 | NodeResLockSet
68
                 | NodeRes String
69
                 | NetworkLockSet
70
                 | Network String
71
                 -- | A lock used for a transitional period when WConfd
72
                 -- keeps the state of the configuration, but all the
73
                 -- operations are still performed on the Python side.
74
                 | ConfigLock
75
                 deriving (Ord, Eq, Show)
76

    
77
-- | Provide the String representation of a lock
78
lockName :: GanetiLocks -> String
79
lockName BGL = "cluster/BGL"
80
lockName ClusterLockSet = "cluster/[lockset]"
81
lockName InstanceLockSet = "instance/[lockset]"
82
lockName NodeAllocLockSet = "node-alloc/[lockset]"
83
lockName NAL = "node-alloc/NAL"
84
lockName (Instance uuid) = "instance/" ++ uuid
85
lockName NodeGroupLockSet = "nodegroup/[lockset]"
86
lockName (NodeGroup uuid) = "nodegroup/" ++ uuid
87
lockName NodeLockSet = "node/[lockset]"
88
lockName (Node uuid) = "node/" ++ uuid
89
lockName NodeResLockSet = "node-res/[lockset]"
90
lockName (NodeRes uuid) = "node-res/" ++ uuid
91
lockName NetworkLockSet = "network/[lockset]"
92
lockName (Network uuid) = "network/" ++ uuid
93
lockName ConfigLock = "cluster/config"
94

    
95
-- | Obtain a lock from its name.
96
lockFromName :: String -> J.Result GanetiLocks
97
lockFromName "cluster/BGL" = return BGL
98
lockFromName "cluster/[lockset]" = return ClusterLockSet
99
lockFromName "instance/[lockset]" = return InstanceLockSet
100
lockFromName (stripPrefix "instance/" -> Just uuid) = return $ Instance uuid
101
lockFromName "nodegroup/[lockset]" = return NodeGroupLockSet
102
lockFromName (stripPrefix "nodegroup/" -> Just uuid) = return $ NodeGroup uuid
103
lockFromName "node-alloc/[lockset]" = return NodeAllocLockSet
104
lockFromName "node-alloc/NAL" = return NAL
105
lockFromName "node-res/[lockset]" = return NodeResLockSet
106
lockFromName (stripPrefix "node-res/" -> Just uuid) = return $ NodeRes uuid
107
lockFromName "node/[lockset]" = return NodeLockSet
108
lockFromName (stripPrefix "node/" -> Just uuid) = return $ Node uuid
109
lockFromName "network/[lockset]" = return NetworkLockSet
110
lockFromName (stripPrefix "network/" -> Just uuid) = return $ Network uuid
111
lockFromName "cluster/config" = return ConfigLock
112
lockFromName n = fail $ "Unknown lock name '" ++ n ++ "'"
113

    
114
instance J.JSON GanetiLocks where
115
  showJSON = J.JSString . J.toJSString . lockName
116
  readJSON = readEitherString >=> lockFromName
117

    
118
-- | The levels, the locks belong to.
119
data LockLevel = LevelCluster
120
               | LevelInstance
121
               | LevelNodeAlloc
122
               | LevelNodeGroup
123
               | LevelNode
124
               | LevelNodeRes
125
               | LevelNetwork
126
               -- | A transitional level for internal configuration locks
127
               | LevelConfig
128
               deriving (Eq, Show, Enum)
129

    
130
-- | Provide the names of the lock levels.
131
lockLevelName :: LockLevel -> String
132
lockLevelName LevelCluster = "cluster"
133
lockLevelName LevelInstance = "instance"
134
lockLevelName LevelNodeAlloc = "node-alloc"
135
lockLevelName LevelNodeGroup = "nodegroup"
136
lockLevelName LevelNode = "node"
137
lockLevelName LevelNodeRes = "node-res"
138
lockLevelName LevelNetwork = "network"
139
lockLevelName LevelConfig = "config"
140

    
141
-- | Obtain a lock level from its name/
142
lockLevelFromName :: String -> J.Result LockLevel
143
lockLevelFromName "cluster" = return LevelCluster
144
lockLevelFromName "instance" = return LevelInstance
145
lockLevelFromName "node-alloc" = return LevelNodeAlloc
146
lockLevelFromName "nodegroup" = return LevelNodeGroup
147
lockLevelFromName "node" = return LevelNode
148
lockLevelFromName "node-res" = return LevelNodeRes
149
lockLevelFromName "network" = return LevelNetwork
150
lockLevelFromName "config" = return LevelConfig
151
lockLevelFromName n = fail $ "Unknown lock-level name '" ++ n ++ "'"
152

    
153
instance J.JSON LockLevel where
154
  showJSON = J.JSString . J.toJSString . lockLevelName
155
  readJSON = readEitherString >=> lockLevelFromName
156

    
157
-- | For a lock, provide its level.
158
lockLevel :: GanetiLocks -> LockLevel
159
lockLevel BGL = LevelCluster
160
lockLevel ClusterLockSet = LevelCluster
161
lockLevel InstanceLockSet = LevelInstance
162
lockLevel NodeAllocLockSet = LevelNodeAlloc
163
lockLevel NAL = LevelNodeAlloc
164
lockLevel (Instance _) = LevelInstance
165
lockLevel NodeGroupLockSet = LevelNodeGroup
166
lockLevel (NodeGroup _) = LevelNodeGroup
167
lockLevel NodeLockSet = LevelNode
168
lockLevel (Node _) = LevelNode
169
lockLevel NodeResLockSet = LevelNodeRes
170
lockLevel (NodeRes _) = LevelNodeRes
171
lockLevel NetworkLockSet = LevelNetwork
172
lockLevel (Network _) = LevelNetwork
173
lockLevel ConfigLock = LevelConfig
174

    
175
instance Lock GanetiLocks where
176
  lockImplications BGL = [ClusterLockSet]
177
  lockImplications (Instance _) = [InstanceLockSet]
178
  lockImplications (NodeGroup _) = [NodeGroupLockSet]
179
  lockImplications NAL = [NodeAllocLockSet]
180
  lockImplications (NodeRes _) = [NodeResLockSet]
181
  lockImplications (Node _) = [NodeLockSet]
182
  lockImplications (Network _) = [NetworkLockSet]
183
  -- the ConfigLock is idependent of everything, it only synchronizes
184
  -- access to the configuration
185
  lockImplications ConfigLock = []
186
  lockImplications _ = []
187

    
188
-- | A client is identified as a job id, thread id and path to its process
189
-- identifier file.
190
--
191
-- The JobId isn't enough to identify a client as the master daemon
192
-- also handles client calls that aren't jobs, but which use the configuration.
193
-- These taks are identified by a unique name, reported to WConfD as a string.
194
data ClientId = ClientId
195
  { ciIdentifier :: Either String JobId
196
  , ciLockFile :: FilePath
197
  }
198
  deriving (Ord, Eq, Show)
199

    
200
-- | Obtain the ClientID from its JSON representation.
201
clientIdFromJSON :: J.JSValue -> J.Result ClientId
202
clientIdFromJSON (J.JSArray [J.JSString s, J.JSString lf]) =
203
  J.Ok . ClientId (Left $ J.fromJSString s) $ J.fromJSString lf
204
clientIdFromJSON (J.JSArray [jsjid, J.JSString lf]) =
205
  J.readJSON jsjid >>= \jid -> J.Ok (ClientId (Right jid) (J.fromJSString lf))
206
clientIdFromJSON x = J.Error $ "malformed client id: " ++ show x
207

    
208
instance J.JSON ClientId where
209
  showJSON (ClientId (Left name) lf) = J.showJSON (name, lf)
210
  showJSON (ClientId (Right jid) lf) = J.showJSON (jid, lf)
211
  readJSON = clientIdFromJSON
212

    
213
-- | The type of lock Allocations in Ganeti. In Ganeti, the owner of
214
-- locks are jobs.
215
type GanetiLockAllocation = LockAllocation GanetiLocks ClientId
216

    
217
-- | Load a lock allocation from disk.
218
loadLockAllocation :: FilePath -> ResultG GanetiLockAllocation
219
loadLockAllocation =
220
  liftIO . readFile
221
  >=> fromJResultE "parsing lock allocation" . J.decodeStrict
222

    
223
-- | Write lock allocation to disk, overwriting any previously lock
224
-- allocation stored there.
225
writeLocks :: (MonadBase IO m, MonadError GanetiException m, MonadLog m)
226
           => FilePath -> GanetiLockAllocation -> m ()
227
writeLocks fpath lockAlloc = do
228
  logDebug "Async. lock allocation writer: Starting write"
229
  toErrorBase . liftIO . atomicWriteFile fpath $ J.encode lockAlloc
230
  logDebug "Async. lock allocation writer: written"
231

    
232
-- | Construct an asynchronous worker whose action is to save the
233
-- current state of the lock allocation.
234
-- The worker's action reads the lock allocation using the given @IO@
235
-- action. Any inbetween changes to the file are tacitly ignored.
236
writeLocksAsyncTask :: FilePath -- ^ Path to the lock file
237
                    -> IO GanetiLockAllocation -- ^ An action to read the
238
                                               -- current lock allocation
239
                    -> ResultG (AsyncWorker ())
240
writeLocksAsyncTask fpath lockAllocAction = mkAsyncWorker $
241
  catchError (do
242
    locks <- liftBase lockAllocAction
243
    writeLocks fpath locks
244
  ) (logEmergency . (++) "Can't write lock allocation status: " . show)