module Ganeti.Network
( PoolPart(..)
, netIpv4NumHosts
, ip4BaseAddr
, getReservedCount
, getFreeCount
, isFull
, getMap
, isReserved
, reserve
, release
, findFree
, allReservations
, reservations
, extReservations
) where
import Control.Monad
import Control.Monad.Error
import Control.Monad.State
import Data.Bits ((.&.))
import Data.Function (on)
import Ganeti.BasicTypes
import qualified Ganeti.Constants as C
import Ganeti.Lens
import Ganeti.Objects
import Ganeti.Objects.Lens
import qualified Ganeti.Objects.BitArray as BA
ip4BaseAddr :: Ip4Network -> Ip4Address
ip4BaseAddr net =
let m = ip4netMask net
mask = 2^(32 :: Integer) 2^(32 m)
in ip4AddressFromNumber . (.&.) mask . ip4AddressToNumber $ ip4netAddr net
ipv4NumHosts :: (Integral n) => n -> Integer
ipv4NumHosts mask = 2^(32 mask)
ipv4NetworkMinNumHosts :: Integer
ipv4NetworkMinNumHosts = ipv4NumHosts C.ipv4NetworkMinSize
ipv4NetworkMaxNumHosts :: Integer
ipv4NetworkMaxNumHosts = ipv4NumHosts C.ipv4NetworkMaxSize
data PoolPart = PoolInstances | PoolExt
addressPoolIso :: Iso' AddressPool BA.BitArray
addressPoolIso = iso apReservations AddressPool
poolLens :: PoolPart -> Lens' Network (Maybe AddressPool)
poolLens PoolInstances = networkReservationsL
poolLens PoolExt = networkExtReservationsL
poolArrayLens :: PoolPart -> Lens' Network (Maybe BA.BitArray)
poolArrayLens part = poolLens part . mapping addressPoolIso
netIpv4NumHosts :: Network -> Integer
netIpv4NumHosts = ipv4NumHosts . ip4netMask . networkNetwork
newPoolArray :: (MonadError e m, Error e) => Network -> m BA.BitArray
newPoolArray net = do
let numhosts = netIpv4NumHosts net
when (numhosts > ipv4NetworkMaxNumHosts) . failError $
"A big network with " ++ show numhosts ++ " host(s) is currently"
++ " not supported, please specify at most a /"
++ show ipv4NetworkMaxNumHosts ++ " network"
when (numhosts < ipv4NetworkMinNumHosts) . failError $
"A network with only " ++ show numhosts ++ " host(s) is too small,"
++ " please specify at least a /"
++ show ipv4NetworkMinNumHosts ++ " network"
return $ BA.zeroes (fromInteger numhosts)
newPool :: (MonadError e m, Error e) => Network -> m AddressPool
newPool = liftM AddressPool . newPoolArray
orNewPool :: (MonadError e m, Error e)
=> Network -> Maybe AddressPool -> m AddressPool
orNewPool net = maybe (newPool net) return
withPool :: (MonadError e m, Error e)
=> PoolPart -> (Network -> BA.BitArray -> m (a, BA.BitArray))
-> StateT Network m a
withPool part f = StateT $ \n -> mapMOf2 (poolLens part) (f' n) n
where
f' net = liftM (over _2 Just)
. mapMOf2 addressPoolIso (f net)
<=< orNewPool net
withPool_ :: (MonadError e m, Error e)
=> PoolPart -> (Network -> BA.BitArray -> m BA.BitArray)
-> Network -> m Network
withPool_ part f = execStateT $ withPool part ((liftM ((,) ()) .) . f)
readPool :: PoolPart -> Network -> Maybe BA.BitArray
readPool = view . poolArrayLens
readPoolE :: (MonadError e m, Error e)
=> PoolPart -> Network -> m BA.BitArray
readPoolE part net =
liftM apReservations $ orNewPool net ((view . poolLens) part net)
readAllE :: (MonadError e m, Error e)
=> Network -> m BA.BitArray
readAllE net = do
let toRes = liftM apReservations . orNewPool net
res <- toRes $ networkReservations net
ext <- toRes $ networkExtReservations net
return $ res BA.-|- ext
reservations :: Network -> Maybe BA.BitArray
reservations = readPool PoolInstances
extReservations :: Network -> Maybe BA.BitArray
extReservations = readPool PoolExt
allReservations :: Network -> Maybe BA.BitArray
allReservations a = (BA.-|-) `liftM` reservations a `ap` extReservations a
getReservedCount :: Network -> Int
getReservedCount = maybe 0 BA.count1 . allReservations
getFreeCount :: Network -> Int
getFreeCount = maybe 0 BA.count0 . allReservations
isFull :: Network -> Bool
isFull = (0 ==) . getFreeCount
getMap :: Network -> String
getMap = maybe "" (BA.asString '.' 'X') . allReservations
addrIndex :: (MonadError e m, Error e) => Ip4Address -> Network -> m Int
addrIndex addr net = do
let n = networkNetwork net
i = on () ip4AddressToNumber addr (ip4BaseAddr n)
when ((i < 0) || (i >= ipv4NumHosts (ip4netMask n))) . failError
$ "Address '" ++ show addr ++ "' not in the network '" ++ show net ++ "'"
return $ fromInteger i
addrAt :: (MonadError e m, Error e) => Int -> Network -> m Ip4Address
addrAt i net | (i' < 0) || (i' >= ipv4NumHosts (ip4netMask n)) =
failError $ "Requested index " ++ show i
++ " outside the range of network '" ++ show net ++ "'"
| otherwise =
return $ ip4AddressFromNumber (ip4AddressToNumber (ip4BaseAddr n) + i')
where
n = networkNetwork net
i' = toInteger i
isReserved :: (MonadError e m, Error e) =>
PoolPart -> Ip4Address -> Network -> m Bool
isReserved part addr net =
(BA.!) `liftM` readPoolE part net `ap` addrIndex addr net
reserve :: (MonadError e m, Error e) =>
PoolPart -> Ip4Address -> Network -> m Network
reserve part addr =
withPool_ part $ \net ba -> do
idx <- addrIndex addr net
let addrs = show addr
when (ba BA.! idx) . failError $ case part of
PoolExt -> "IP " ++ addrs ++ " is already externally reserved"
PoolInstances -> "IP " ++ addrs ++ " is already used by an instance"
BA.setAt idx True ba
release :: (MonadError e m, Error e) =>
PoolPart -> Ip4Address -> Network -> m Network
release part addr =
withPool_ part $ \net ba -> do
idx <- addrIndex addr net
let addrs = show addr
unless (ba BA.! idx) . failError $ case part of
PoolExt -> "IP " ++ addrs ++ " is not externally reserved"
PoolInstances -> "IP " ++ addrs ++ " is not used by an instance"
BA.setAt idx False ba
findFree :: (MonadError e m, Error e)
=> (Ip4Address -> Bool) -> Network -> m (Maybe Ip4Address)
findFree p net = readAllE net >>= BA.foldr f (return Nothing)
where
addrAtEither = addrAt :: Int -> Network -> Either String Ip4Address
f False i _ | Right a <- addrAtEither i net, p a = return (Just a)
f _ _ x = x