2013-07-25 16:44:23 +04:00
|
|
|
{-# LANGUAGE NoImplicitPrelude #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
2012-08-06 18:44:41 +04:00
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
2013-07-25 16:44:23 +04:00
|
|
|
{-# LANGUAGE TemplateHaskell #-}
|
2013-07-25 14:07:48 +04:00
|
|
|
module Keter.HostManager
|
2012-08-06 18:44:41 +04:00
|
|
|
( -- * Types
|
2013-07-25 16:44:23 +04:00
|
|
|
HostManager
|
|
|
|
, Reservations
|
|
|
|
, Conflicts
|
2012-08-06 18:44:41 +04:00
|
|
|
-- * Actions
|
2013-07-25 16:44:23 +04:00
|
|
|
, reserveHosts
|
|
|
|
, forgetReservations
|
|
|
|
, activateApp
|
|
|
|
, lookupAction
|
2012-08-06 18:44:41 +04:00
|
|
|
-- * Initialize
|
|
|
|
, start
|
|
|
|
) where
|
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
import Control.Applicative
|
|
|
|
import qualified Control.Concurrent.MVar as M
|
|
|
|
import Control.Exception (assert)
|
|
|
|
import Data.ByteString.Char8 ()
|
|
|
|
import Data.Either (partitionEithers)
|
|
|
|
import qualified Data.Map as Map
|
|
|
|
import qualified Data.Set as Set
|
|
|
|
import Data.Text.Encoding (encodeUtf8)
|
|
|
|
import Keter.Prelude
|
|
|
|
import Keter.Types
|
|
|
|
import Prelude (null)
|
|
|
|
import Prelude (IO)
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
type HMState = Map.Map HostBS HostValue
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
data HostValue = HVActive !Appname !ProxyAction
|
|
|
|
| HVReserved !Appname
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
newtype HostManager = HostManager (MVar HMState) -- FIXME use an IORef instead
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
type Conflicts = Map.Map Host Appname
|
|
|
|
type Reservations = Set.Set Host
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
start :: IO HostManager
|
|
|
|
start = HostManager <$> M.newMVar Map.empty
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
-- | Reserve the given hosts so that no other application may use them. Does
|
|
|
|
-- not yet enable any action. The semantics are:
|
|
|
|
--
|
|
|
|
-- 1. If a requested host is currently actively used or by an app of the same name, it is
|
|
|
|
-- considered reserved.
|
|
|
|
--
|
|
|
|
-- 2. If a requested host is currently reserved by an app of the same name, it
|
|
|
|
-- is considered an error in calling this API. Only one app reservation can
|
|
|
|
-- happen at a time.
|
|
|
|
--
|
|
|
|
-- 3. If any requested host is currently used or reserved by an app with a
|
|
|
|
-- different name, then those values are returned as @Left@.
|
|
|
|
--
|
|
|
|
-- 4. Otherwise, the hosts which were reserved are returned as @Right@. This
|
|
|
|
-- does /not/ include previously active hosts.
|
|
|
|
reserveHosts :: HostManager
|
|
|
|
-> Appname
|
|
|
|
-> Set.Set Host
|
|
|
|
-> KIO (Either Conflicts Reservations)
|
|
|
|
reserveHosts (HostManager mstate) app hosts = modifyMVar mstate $ \entries0 ->
|
|
|
|
return $ case partitionEithers $ map (checkHost entries0) $ Set.toList hosts of
|
|
|
|
([], toReserve) ->
|
|
|
|
(Set.foldr reserve entries0 $ Set.unions toReserve, Right Set.empty)
|
|
|
|
(conflicts, _) -> (entries0, Left $ Map.fromList conflicts)
|
|
|
|
where
|
|
|
|
checkHost entries0 host =
|
|
|
|
case Map.lookup (encodeUtf8 host) entries0 of
|
|
|
|
Nothing -> Right $ Set.singleton host
|
|
|
|
Just (HVReserved app') -> assert (app /= app')
|
|
|
|
$ Left (host, app')
|
|
|
|
Just (HVActive app' _)
|
|
|
|
| app == app' -> Right Set.empty
|
|
|
|
| otherwise -> Left (host, app')
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
hvres = HVReserved app
|
|
|
|
reserve host es =
|
|
|
|
assert (Map.notMember hostBS es) $ Map.insert hostBS hvres es
|
|
|
|
where
|
|
|
|
hostBS = encodeUtf8 host
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
-- | Forget previously made reservations.
|
|
|
|
forgetReservations :: HostManager
|
|
|
|
-> Appname
|
|
|
|
-> Reservations
|
|
|
|
-> KIO ()
|
|
|
|
forgetReservations (HostManager mstate) app hosts = modifyMVar_ mstate $ \state0 ->
|
|
|
|
return $ Set.foldr forget state0 hosts
|
|
|
|
where
|
|
|
|
forget host state =
|
|
|
|
assert isReservedByMe $ Map.delete hostBS state
|
|
|
|
where
|
|
|
|
hostBS = encodeUtf8 host
|
|
|
|
isReservedByMe =
|
|
|
|
case Map.lookup hostBS state of
|
|
|
|
Nothing -> False
|
|
|
|
Just (HVReserved app') -> app == app'
|
|
|
|
Just HVActive{} -> False
|
2012-10-21 09:07:26 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
-- | Activate a new app. Note that you /must/ first reserve the hostnames you'll be using.
|
|
|
|
activateApp :: HostManager
|
|
|
|
-> Appname
|
|
|
|
-> Map.Map Host ProxyAction
|
|
|
|
-> KIO ()
|
|
|
|
activateApp (HostManager mstate) app actions = modifyMVar_ mstate $ \state0 ->
|
|
|
|
return $ Map.foldrWithKey activate state0 actions
|
|
|
|
where
|
|
|
|
activate host action state =
|
|
|
|
assert isOwnedByMe $ Map.insert hostBS (HVActive app action) state
|
|
|
|
where
|
|
|
|
hostBS = encodeUtf8 host
|
|
|
|
isOwnedByMe =
|
|
|
|
case Map.lookup hostBS state of
|
|
|
|
Nothing -> False
|
|
|
|
Just (HVReserved app') -> app == app'
|
|
|
|
Just (HVActive app' _) -> app == app'
|
2012-08-06 18:44:41 +04:00
|
|
|
|
2013-07-25 16:44:23 +04:00
|
|
|
lookupAction :: HostManager
|
|
|
|
-> HostBS
|
2013-07-26 12:17:05 +04:00
|
|
|
-> IO (Maybe ProxyAction)
|
|
|
|
lookupAction (HostManager mstate) host = M.withMVar mstate $ \state ->
|
2013-07-25 16:44:23 +04:00
|
|
|
return $ case Map.lookup host state of
|
|
|
|
Nothing -> Nothing
|
|
|
|
Just (HVActive _ action) -> Just action
|
|
|
|
Just (HVReserved _) -> Nothing
|