.. that follow the original Python implementation.

Signed-off-by: Petr Pudlak <[email protected]>
---
 src/Ganeti/WConfd/TempRes.hs | 145 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 141 insertions(+), 4 deletions(-)

diff --git a/src/Ganeti/WConfd/TempRes.hs b/src/Ganeti/WConfd/TempRes.hs
index 96a3bf9..06569cd 100644
--- a/src/Ganeti/WConfd/TempRes.hs
+++ b/src/Ganeti/WConfd/TempRes.hs
@@ -2,6 +2,9 @@
 
 {-| Pure functions for manipulating reservations of temporary objects
 
+NOTE: Reservations aren't released specifically, they're just all
+released at the end of a job. This could be improved in the future.
+
 -}
 
 {-
@@ -43,6 +46,14 @@ module Ganeti.WConfd.TempRes
   , reserveMAC
   , generateDRBDSecret
   , reserveLV
+  , IPv4ResAction(..)
+  , IPv4Reservation(..)
+  , reserveIp
+  , releaseIp
+  , generateIp
+  , commitReleaseIp
+  , commitReservedIps
+  , listReservedIps
   , dropAllReservations
   , isReserved
   , reserve
@@ -70,9 +81,12 @@ import qualified Ganeti.Constants as C
 import Ganeti.Errors
 import qualified Ganeti.JSON as J
 import Ganeti.Lens
+import qualified Ganeti.Network as N
 import Ganeti.Locking.Locks (ClientId)
+import Ganeti.Logging
 import Ganeti.Objects
 import Ganeti.THH
+import Ganeti.Objects.Lens (configNetworksL)
 import Ganeti.Utils
 import Ganeti.Utils.MonadPlus
 import Ganeti.Utils.Random
@@ -98,6 +112,33 @@ type DRBDMap' = Map NodeUUID (Map DRBDMinor [InstanceUUID])
 
 -- * The state data structure
 
+-- | Types of IPv4 reservation actions.
+data IPv4ResAction = IPv4Reserve | IPv4Release
+  deriving (Eq, Ord, Show, Bounded, Enum)
+
+instance J.JSON IPv4ResAction where
+  showJSON IPv4Reserve = J.JSString . J.toJSString $ C.reserveAction
+  showJSON IPv4Release = J.JSString . J.toJSString $ C.releaseAction
+  readJSON = J.readEitherString
+             >=> \s -> case () of
+                    _ | s == C.reserveAction -> return IPv4Reserve
+                      | s == C.releaseAction -> return IPv4Release
+                      | otherwise -> fail $ "Invalid IP reservation action: "
+                                           ++ s
+
+-- | The values stored in the IPv4 reservation table.
+data IPv4Reservation = IPv4Res
+  { ipv4ResAction :: IPv4ResAction
+  , ipv4ResNetwork :: NetworkUUID
+  , ipv4ResAddr :: Ip4Address
+  } deriving (Eq, Ord, Show)
+
+instance J.JSON IPv4Reservation where
+  -- Notice that addr and net are in a different order, to be compatible
+  -- with the original Python representation (while it's used).
+  showJSON (IPv4Res a net addr) = J.showJSON (a, addr, net)
+  readJSON = fmap (\(a, addr, net) -> IPv4Res a net addr) . J.readJSON
+
 -- | A polymorphic data structure for managing temporary resources assigned
 -- to jobs.
 newtype TempRes j a = TempRes { getTempRes :: MM.MultiMap j a }
@@ -112,15 +153,16 @@ instance (J.JSON j, Ord j, J.JSON a, Ord a) => J.JSON 
(TempRes j a) where
   readJSON = liftM TempRes . J.readJSON
 
 -- | The state of the temporary reservations
-$(buildObject "TempResState" "trs" $
+$(buildObject "TempResState" "trs"
   [ simpleField "dRBD"             [t| DRBDMap |]
   , simpleField "mACs"             [t| TempRes ClientId MAC |]
   , simpleField "dRBDSecrets"      [t| TempRes ClientId DRBDSecret |]
   , simpleField "lVs"              [t| TempRes ClientId LogicalVolume |]
+  , simpleField "iPv4s"            [t| TempRes ClientId IPv4Reservation |]
   ])
 
 emptyTempResState :: TempResState
-emptyTempResState = TempResState M.empty mempty mempty mempty
+emptyTempResState = TempResState M.empty mempty mempty mempty mempty
 
 $(makeCustomLenses ''TempResState)
 
@@ -130,14 +172,19 @@ $(makeCustomLenses ''TempResState)
 resError :: (MonadError GanetiException m) => String -> m a
 resError = throwError . ReservationError
 
+-- | Converts 'GenericError' into a 'ReservationError'.
+toResError :: (MonadError GanetiException m) => m a -> m a
+toResError = flip catchError (throwError . f)
+  where
+    f (GenericError msg) = ReservationError msg
+    f e = e
+
 -- | Filter values from the nested map and remove any nested maps
 -- that become empty.
 filterNested :: (Ord a, Ord b)
              => (c -> Bool) -> Map a (Map b c) -> Map a (Map b c)
 filterNested p = M.filter (not . M.null) . fmap (M.filter p)
 
--- * DRBDs
-
 -- | Converts a lens that works on maybe values into a lens that works
 -- on regular ones. A missing value on the input is replaced by
 -- 'mempty'.
@@ -221,6 +268,9 @@ reserve jobid x tr = do
 dropReservationsFor :: (Ord a, Ord j) => j -> TempRes j a -> TempRes j a
 dropReservationsFor jobid = TempRes . MM.deleteAll jobid . getTempRes
 
+reservedFor :: (Ord a, Ord j) => j -> TempRes j a -> S.Set a
+reservedFor jobid = MM.lookup jobid . getTempRes
+
 reserved :: (Ord a, Ord j) => TempRes j a -> S.Set a
 reserved = MM.values . getTempRes
 
@@ -276,6 +326,7 @@ dropAllReservations jobId = modify $
     (trsMACsL %~ dropReservationsFor jobId)
   . (trsDRBDSecretsL %~ dropReservationsFor jobId)
   . (trsLVsL %~ dropReservationsFor jobId)
+  . (trsIPv4sL %~ dropReservationsFor jobId)
 
 -- | Looks up a network by its UUID.
 lookupNetwork :: (MonadError GanetiException m)
@@ -343,3 +394,89 @@ reserveLV jobId lv cd = do
   when (S.member lv existing)
     $ resError "MAC already in use"
   modifyM $ traverseOf trsLVsL (reserve jobId lv)
+
+-- ** IPv4 addresses
+
+-- | Lists all IPv4 addresses reserved for a given network.
+usedIPv4Addrs :: NetworkUUID -> S.Set IPv4Reservation -> S.Set Ip4Address
+usedIPv4Addrs netuuid =
+  S.map ipv4ResAddr . S.filter ((== netuuid) . ipv4ResNetwork)
+
+-- | Reserve a given IPv4 address for use by an instance.
+reserveIp
+  :: (MonadError GanetiException m, MonadState TempResState m, Functor m)
+  => ClientId -> NetworkUUID -> Ip4Address
+  -> Bool -- ^ whether to check externally reserved IPs
+  -> ConfigData -> m ()
+reserveIp jobId netuuid addr checkExt cd = toResError $ do
+    net <- lookupNetwork cd netuuid
+    isres <- N.isReserved N.PoolInstances addr net
+    when isres . resError $ "IP address already in use"
+    when checkExt $ do
+      isextres <- N.isReserved N.PoolExt addr net
+      when isextres . resError $ "IP is externally reserved"
+    let action = IPv4Res IPv4Reserve netuuid addr
+    modifyM $ traverseOf trsIPv4sL (reserve jobId action)
+
+-- | Give a specific IP address back to an IP pool.
+-- The IP address is returned to the IP pool designated by network id
+-- and marked as reserved.
+releaseIp
+  :: (MonadError GanetiException m, MonadState TempResState m, Functor m)
+  => ClientId -> NetworkUUID -> Ip4Address -> m ()
+releaseIp jobId netuuid addr =
+  let action = IPv4Res { ipv4ResAction = IPv4Release
+                       , ipv4ResNetwork = netuuid
+                       , ipv4ResAddr = addr }
+  in modifyM $ traverseOf trsIPv4sL (reserve jobId action)
+
+-- Find a free IPv4 address for an instance and reserve it.
+generateIp
+  :: (MonadError GanetiException m, MonadState TempResState m, Functor m)
+  => ClientId -> NetworkUUID -> ConfigData -> m Ip4Address
+generateIp jobId netuuid cd = toResError $ do
+    net <- lookupNetwork cd netuuid
+    let f res = do
+          let ips = usedIPv4Addrs netuuid res
+          addr <- N.findFree (`S.notMember` ips) net
+          maybe (resError "Cannot generate IP. Network is full")
+                (return . IPv4Res IPv4Reserve netuuid) addr
+    liftM ipv4ResAddr . stateM $ traverseOf2 trsIPv4sL (withReserved jobId f)
+
+-- | Commit a reserved/released IP address to an IP pool.
+-- The IP address is taken from the network's IP pool and marked as
+-- reserved/free for instances.
+commitIp
+  :: (MonadError GanetiException m, Functor m)
+  => IPv4Reservation -> ConfigData -> m ConfigData
+commitIp (IPv4Res actType netuuid addr) cd = toResError $ do
+    let call = case actType of
+                IPv4Reserve -> N.reserve
+                IPv4Release -> N.release
+        f Nothing = resError $ "Network '" ++ show netuuid ++ "' not found"
+        f (Just net) = Just `liftM` call N.PoolInstances addr net
+    traverseOf (configNetworksL . J.alterContainerL netuuid) f cd
+
+-- | Immediately release an IP address, without using the reservations pool.
+commitReleaseIp
+  :: (MonadError GanetiException m, Functor m)
+  => NetworkUUID -> Ip4Address -> ConfigData -> m ConfigData
+commitReleaseIp netuuid addr =
+  commitIp (IPv4Res IPv4Release netuuid addr)
+
+-- | Commit all reserved/released IP address to an IP pool.
+-- The IP addresses are taken from the network's IP pool and marked as
+-- reserved/free for instances.
+--
+-- Note that the reservations are kept, they are supposed to be cleaned
+-- when a job finishes.
+commitReservedIps
+  :: (MonadError GanetiException m, Functor m, MonadLog m)
+  => ClientId -> TempResState -> ConfigData -> m ConfigData
+commitReservedIps jobId tr cd = do
+  let res = reservedFor jobId (trsIPv4s tr)
+  logDebug $ "Commiting reservations: " ++ show res
+  F.foldrM commitIp cd res
+
+listReservedIps :: ClientId -> TempResState -> S.Set IPv4Reservation
+listReservedIps jobid = reservedFor jobid . trsIPv4s
-- 
2.0.0.526.g5318336

Reply via email to