Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Locking / Allocation.hs @ 2754dc7d

History | View | Annotate | Download (15.8 kB)

1
{-| Implementation of lock allocation.
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2014 Google Inc.
8

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

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

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

    
24
-}
25

    
26
module Ganeti.Locking.Allocation
27
  ( LockAllocation
28
  , emptyAllocation
29
  , OwnerState(..)
30
  , lockOwners
31
  , listLocks
32
  , LockRequest(..)
33
  , requestExclusive
34
  , requestShared
35
  , requestRelease
36
  , updateLocks
37
  , freeLocks
38
  , intersectLocks
39
  , opportunisticLockUnion
40
  ) where
41

    
42
import Control.Applicative (liftA2, (<$>), (<*>))
43
import Control.Arrow (second, (***))
44
import Control.Monad
45
import Data.Foldable (for_, find)
46
import Data.List (sort)
47
import qualified Data.Map as M
48
import Data.Maybe (fromMaybe)
49
import qualified Data.Set as S
50
import qualified Text.JSON as J
51

    
52
import Ganeti.BasicTypes
53
import Ganeti.JSON (toArray)
54
import Ganeti.Locking.Types
55

    
56
{-
57

    
58
This module is parametric in the type of locks and lock owners.
59
While we only state minimal requirements for the types, we will
60
consistently use the type variable 'a' for the type of locks and
61
the variable 'b' for the type of the lock owners throughout this
62
module.
63

    
64
-}
65

    
66
-- | Data type describing the way a lock can be owned.
67
data OwnerState = OwnShared | OwnExclusive deriving (Ord, Eq, Show)
68

    
69
-- | Type describing indirect ownership on a lock. We keep the set
70
-- of all (lock, owner)-pairs for locks that are implied in the given
71
-- lock, annotated with the type of ownership (shared or exclusive).
72
type IndirectOwners a b = M.Map (a, b) OwnerState
73

    
74
-- | The state of a lock that is taken. Besides the state of the lock
75
-- itself, we also keep track of all other lock allocation that affect
76
-- the given lock by means of implication.
77
data AllocationState a b = Exclusive b (IndirectOwners a b)
78
                         | Shared (S.Set b) (IndirectOwners a b)
79
                         deriving (Eq, Show)
80

    
81
-- | Compute the set of indirect owners from the information about
82
-- indirect ownership.
83
indirectOwners :: (Ord a, Ord b) => M.Map (a, b) OwnerState -> S.Set b
84
indirectOwners = S.map snd . M.keysSet
85

    
86
-- | Compute the (zero or one-elment) set of exclusive indirect owners.
87
indirectExclusives :: (Ord a, Ord b) => M.Map (a, b) OwnerState -> S.Set b
88
indirectExclusives = indirectOwners . M.filter (== OwnExclusive)
89

    
90
{-| Representation of a Lock allocation
91

    
92
To keep queries for locks efficient, we keep two
93
associations, with the invariant that they fit
94
together: the association from locks to their
95
allocation state, and the association from an
96
owner to the set of locks owned. As we do not
97
export the constructor, the problem of keeping
98
this invariant reduces to only exporting functions
99
that keep the invariant.
100

    
101
-}
102

    
103
data LockAllocation a b =
104
  LockAllocation { laLocks :: M.Map a (AllocationState a b)
105
                 , laOwned :: M.Map b (M.Map a OwnerState)
106
                 }
107
  deriving (Eq, Show)
108

    
109
-- | A state with all locks being free.
110
emptyAllocation :: (Ord a, Ord b) => LockAllocation a b
111
emptyAllocation =
112
  LockAllocation { laLocks = M.empty
113
                 , laOwned = M.empty
114
                 }
115

    
116
-- | Obtain the list of all owners holding at least a single lock.
117
lockOwners :: Ord b => LockAllocation a b -> [b]
118
lockOwners = M.keys . laOwned
119

    
120
-- | Obtain the locks held by a given owner. The locks are reported
121
-- as a map from the owned locks to the form of ownership (OwnShared
122
-- or OwnExclusive).
123
listLocks :: Ord b => b -> LockAllocation a b -> M.Map a OwnerState
124
listLocks owner = fromMaybe M.empty . M.lookup owner . laOwned
125

    
126
-- | Data Type describing a change request on a single lock.
127
data LockRequest a = LockRequest { lockAffected :: a
128
                                 , lockRequestType :: Maybe OwnerState
129
                                 }
130
                     deriving (Eq, Show)
131

    
132
-- | Lock request for an exclusive lock.
133
requestExclusive :: a -> LockRequest a
134
requestExclusive lock = LockRequest { lockAffected = lock
135
                                    , lockRequestType = Just OwnExclusive }
136

    
137
-- | Lock request for a shared lock.
138
requestShared :: a -> LockRequest a
139
requestShared lock = LockRequest { lockAffected = lock
140
                                 , lockRequestType = Just OwnShared }
141

    
142
-- | Request to release a lock.
143
requestRelease :: a -> LockRequest a
144
requestRelease lock = LockRequest { lockAffected = lock
145
                                  , lockRequestType = Nothing }
146

    
147
-- | Update the Allocation state of a lock according to a given
148
-- function.
149
updateAllocState :: (Ord a, Ord b)
150
                  => (Maybe (AllocationState a b) -> AllocationState a b)
151
                  -> LockAllocation a b -> a -> LockAllocation a b
152
updateAllocState f state lock =
153
  let locks' = M.alter (find (/= Shared S.empty M.empty) . Just . f)
154
                        lock (laLocks state)
155
  in state { laLocks = locks' }
156

    
157
-- | Internal function to update the state according to a single
158
-- lock request, assuming all prerequisites are met.
159
updateLock :: (Ord a, Ord b)
160
           => b
161
           -> LockAllocation a b -> LockRequest a -> LockAllocation a b
162
updateLock owner state (LockRequest lock (Just OwnExclusive)) =
163
  let locks = laLocks state
164
      lockstate' = case M.lookup lock locks of
165
        Just (Exclusive _ i) -> Exclusive owner i
166
        Just (Shared _ i) -> Exclusive owner i
167
        Nothing -> Exclusive owner M.empty
168
      locks' = M.insert lock lockstate' locks
169
      ownersLocks' = M.insert lock OwnExclusive $ listLocks owner state
170
      owned' = M.insert owner ownersLocks' $ laOwned state
171
  in state { laLocks = locks', laOwned = owned' }
172
updateLock owner state (LockRequest lock (Just OwnShared)) =
173
  let ownersLocks' = M.insert lock OwnShared $ listLocks owner state
174
      owned' = M.insert owner ownersLocks' $ laOwned state
175
      locks = laLocks state
176
      lockState' = case M.lookup lock locks of
177
        Just (Exclusive _ i) -> Shared (S.singleton owner) i
178
        Just (Shared s i) -> Shared (S.insert owner s) i
179
        _ -> Shared (S.singleton owner) M.empty
180
      locks' = M.insert lock lockState' locks
181
  in state { laLocks = locks', laOwned = owned' }
182
updateLock owner state (LockRequest lock Nothing) =
183
  let ownersLocks' = M.delete lock $ listLocks owner state
184
      owned = laOwned state
185
      owned' = if M.null ownersLocks'
186
                 then M.delete owner owned
187
                 else M.insert owner ownersLocks' owned
188
      update (Just (Exclusive x i)) = if x == owner
189
                                        then Shared S.empty i
190
                                        else Exclusive x i
191
      update (Just (Shared s i)) = Shared (S.delete owner s) i
192
      update Nothing = Shared S.empty M.empty
193
  in updateAllocState update (state { laOwned = owned' }) lock
194

    
195
-- | Update the set of indirect ownerships of a lock by the given function.
196
updateIndirectSet :: (Ord a, Ord b)
197
                  => (IndirectOwners a b -> IndirectOwners a b)
198
                  -> LockAllocation a b -> a -> LockAllocation a b
199
updateIndirectSet f =
200
  let update (Just (Exclusive x i)) = Exclusive x (f i)
201
      update (Just (Shared s i)) = Shared s (f i)
202
      update Nothing = Shared S.empty (f M.empty)
203
  in updateAllocState update
204

    
205
-- | Update all indirect onwerships of a given lock.
206
updateIndirects :: (Lock a, Ord b)
207
                => b
208
                -> LockAllocation a b -> LockRequest a -> LockAllocation a b
209
updateIndirects owner state req =
210
  let lock = lockAffected req
211
      fn = case lockRequestType req of
212
             Nothing -> M.delete (lock, owner)
213
             Just tp -> M.insert (lock, owner) tp
214
  in foldl (updateIndirectSet fn) state $ lockImplications lock
215

    
216
-- | Update the locks of an owner according to the given request. Return
217
-- the pair of the new state and the result of the operation, which is the
218
-- the set of owners on which the operation was blocked on. so an empty set is
219
-- success, and the state is updated if, and only if, the returned set is emtpy.
220
-- In that way, it can be used in atomicModifyIORef.
221
updateLocks :: (Lock a, Ord b)
222
            => b
223
            -> [LockRequest a]
224
            -> LockAllocation a b -> (LockAllocation a b, Result (S.Set b))
225
updateLocks owner reqs state = genericResult ((,) state . Bad) (second Ok) $ do
226
  unless ((==) (length reqs) . S.size . S.fromList $ map lockAffected reqs)
227
    . runListHead (return ())
228
                  (fail . (++) "Inconsitent requests for lock " . show) $ do
229
      r <- reqs
230
      r' <- reqs
231
      guard $ r /= r'
232
      guard $ lockAffected r == lockAffected r'
233
      return $ lockAffected r
234
  let current = listLocks owner state
235
  unless (M.null current) $ do
236
    let (highest, _) = M.findMax current
237
        notHolding = not
238
                     . any (uncurry (==) . ((M.lookup `flip` current) *** Just))
239
        orderViolation l = fail $ "Order violation: requesting " ++ show l
240
                                   ++ " while holding " ++ show highest
241
    for_ reqs $ \req -> case req of
242
      LockRequest lock (Just OwnExclusive)
243
        | lock < highest && notHolding ((,) <$> lock : lockImplications lock
244
                                            <*> [OwnExclusive])
245
        -> orderViolation lock
246
      LockRequest lock (Just OwnShared)
247
        | lock < highest && notHolding ((,) <$> lock : lockImplications lock
248
                                            <*> [OwnExclusive, OwnShared])
249
        -> orderViolation lock
250
      _ -> Ok ()
251
  let sharedsHeld = M.keysSet $ M.filter (== OwnShared) current
252
      exclusivesRequested = map lockAffected
253
                            . filter ((== Just OwnExclusive) . lockRequestType)
254
                            $ reqs
255
  runListHead (return ()) fail $ do
256
    x <- exclusivesRequested
257
    i <- lockImplications x
258
    guard $ S.member i sharedsHeld
259
    return $ "Order violation: requesting exclusively " ++ show x
260
              ++ " while holding a shared lock on the group lock " ++ show i
261
              ++ " it belongs to."
262
  let blockedOn (LockRequest  _ Nothing) = S.empty
263
      blockedOn (LockRequest lock (Just OwnExclusive)) =
264
        case M.lookup lock (laLocks state) of
265
          Just (Exclusive x i) ->
266
            S.singleton x `S.union` indirectOwners i
267
          Just (Shared xs i) ->
268
            xs `S.union` indirectOwners i
269
          _ -> S.empty
270
      blockedOn (LockRequest lock (Just OwnShared)) =
271
        case M.lookup lock (laLocks state) of
272
          Just (Exclusive x i) ->
273
            S.singleton x `S.union` indirectExclusives i
274
          Just (Shared _ i) -> indirectExclusives i
275
          _ -> S.empty
276
  let indirectBlocked Nothing _ = S.empty
277
      indirectBlocked (Just OwnShared) lock =
278
        case M.lookup lock (laLocks state) of
279
          Just (Exclusive x _) -> S.singleton x
280
          _ -> S.empty
281
      indirectBlocked (Just OwnExclusive) lock =
282
        case M.lookup lock (laLocks state) of
283
          Just (Exclusive x _) -> S.singleton x
284
          Just (Shared xs _) -> xs
285
          _ -> S.empty
286
  let direct = S.unions $ map blockedOn reqs
287
      indirect = reqs >>= \req ->
288
        map (indirectBlocked (lockRequestType req))
289
          . lockImplications $ lockAffected req
290
  let blocked = S.delete owner . S.unions $ direct:indirect
291
  let state' = foldl (updateLock owner) state reqs
292
      state'' = foldl (updateIndirects owner) state' reqs
293
  return (if S.null blocked then state'' else state, blocked)
294

    
295
-- | Compute the state after an onwer releases all its locks.
296
freeLocks :: (Lock a, Ord b) => LockAllocation a b -> b -> LockAllocation a b
297
freeLocks state owner =
298
  fst . flip (updateLocks owner) state . map requestRelease . M.keys
299
    $ listLocks owner state
300

    
301
-- | Restrict the locks of a user to a given set.
302
intersectLocks :: (Lock a, Ord b) => b -> [a]
303
               -> LockAllocation a b -> LockAllocation a b
304
intersectLocks owner locks state =
305
  let lockset = S.fromList locks
306
      toFree = filter (not . flip S.member lockset)
307
                 . M.keys $ listLocks owner state
308
  in fst $ updateLocks owner (map requestRelease toFree) state
309

    
310
-- | Opportunistically allocate locks for a given user; return the set
311
-- of actually acquired. The signature is chosen to be suitable for
312
-- atomicModifyIORef.
313
opportunisticLockUnion :: (Lock a, Ord b)
314
                       => b -> [(a, OwnerState)]
315
                       -> LockAllocation a b -> (LockAllocation a b, S.Set a)
316
opportunisticLockUnion owner reqs state =
317
  let locks = listLocks owner state
318
      reqs' = sort $ filter (uncurry (<) . (flip M.lookup locks *** Just)) reqs
319
      maybeAllocate (s, success) (lock, ownstate) =
320
        let (s', result) = updateLocks owner
321
                                       [(if ownstate == OwnShared
322
                                           then requestShared
323
                                           else requestExclusive) lock]
324
                                       s
325
        in (s', if result == Ok S.empty then lock:success else success)
326
  in second S.fromList $ foldl maybeAllocate (state, []) reqs'
327

    
328
{-| Serializaiton of Lock Allocations
329

    
330
To serialize a lock allocation, we only remember which owner holds
331
which locks at which level (shared or exclusive). From this information,
332
everything else can be reconstructed, simply using updateLocks.
333
-}
334

    
335
instance J.JSON OwnerState where
336
  showJSON OwnShared = J.showJSON "shared"
337
  showJSON OwnExclusive = J.showJSON "exclusive"
338
  readJSON (J.JSString x) = let s = J.fromJSString x
339
                            in case s of
340
                              "shared" -> J.Ok OwnShared
341
                              "exclusive" -> J.Ok OwnExclusive
342
                              _ -> J.Error $ "Unknown owner type " ++ s
343
  readJSON _ = J.Error "Owner type not encoded as a string"
344

    
345
-- | Read a lock-ownerstate pair from JSON.
346
readLockOwnerstate :: (J.JSON a) => J.JSValue -> J.Result (a, OwnerState)
347
readLockOwnerstate (J.JSArray [x, y]) = liftA2 (,) (J.readJSON x) (J.readJSON y)
348
readLockOwnerstate x = fail $ "lock-ownerstate pairs are encoded as arrays"
349
                              ++ " of length 2, but found " ++ show x
350

    
351
-- | Read an owner-lock pair from JSON.
352
readOwnerLock :: (J.JSON a, J.JSON b)
353
              => J.JSValue -> J.Result (b, [(a, OwnerState)])
354
readOwnerLock (J.JSArray [x, J.JSArray ys]) =
355
  liftA2 (,) (J.readJSON x) (mapM readLockOwnerstate ys)
356
readOwnerLock x = fail $ "Expected pair of owner and list of owned locks,"
357
                         ++ " but found " ++ show x
358

    
359
-- | Transform a lock-ownerstate pair into a LockRequest.
360
toRequest :: (a, OwnerState) -> LockRequest a
361
toRequest (a, OwnExclusive) = requestExclusive a
362
toRequest (a, OwnShared) = requestShared a
363

    
364
-- | Obtain a LockAllocation from a given owner-locks list.
365
-- The obtained allocation is the one obtained if the respective owners
366
-- requested their locks sequentially.
367
allocationFromOwners :: (Lock a, Ord b, Show b)
368
                     => [(b, [(a, OwnerState)])]
369
                     -> J.Result (LockAllocation a b)
370
allocationFromOwners =
371
  let allocateOneOwner s (o, req) = do
372
        let (s', result) = updateLocks o (map toRequest req) s
373
        when (result /= Ok S.empty) . fail
374
          . (++) ("Inconsistent lock status for " ++ show o ++ ": ")
375
          $ case result of
376
            Bad err -> err
377
            Ok blocked -> "blocked on " ++ show (S.toList blocked)
378
        return s'
379
  in foldM allocateOneOwner emptyAllocation
380

    
381
instance (Lock a, J.JSON a, Ord b, J.JSON b, Show b)
382
           => J.JSON (LockAllocation a b) where
383
  showJSON = J.showJSON . M.toList . M.map M.toList . laOwned
384
  readJSON x = do
385
    xs <- toArray x
386
    owned <- mapM readOwnerLock xs
387
    allocationFromOwners owned