-- This file is part of zhk -- -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, -- are permitted provided that the following conditions are met: -- -- Redistributions of source code must retain the above copyright notice, this -- list of conditions and the following disclaimer. -- -- Redistributions in binary form must reproduce the above copyright notice, this -- list of conditions and the following disclaimer in the documentation and/or -- other materials provided with the distribution. -- -- Neither the name of the {organization} nor the names of its -- contributors may be used to endorse or promote products derived from -- this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- | Zookeeper client library module Database.Zookeeper ( -- * Description -- $description -- * Example -- $example -- * Notes -- $notes -- * Connection addAuth , setWatcher , withZookeeper -- * Configuration/State , getState , getClientId , setDebugLevel , getRecvTimeout -- * Reading , get , exists , getAcl , getChildren -- * Writing , set , create , delete , setAcl -- * Types , Scheme , Timeout , Watcher , ClientID (..) , Zookeeper () , Acl (..) , Perm (..) , Stat (..) , Event (..) , State (..) , AclList (..) , Version , ZLogLevel (..) , CreateFlag (..) -- ** Error values , ZKError (..) ) where import Foreign import Foreign.C import Control.Monad import qualified Data.ByteString as B import Control.Exception import Database.Zookeeper.CApi import Database.Zookeeper.Types -- | Connects to the zookeeper cluster. This function may throw an -- exception if a valid zookeeper handle could not be created. -- -- The connection is terminated right before this function returns. withZookeeper :: String -- ^ The zookeeper endpoint to connect to. This is given -- as-is to the underlying C API. Briefly, host:port -- separated by comma. At the end, you may define an -- optional chroot, like the following: -- localhost:2181,localhost:2182/foobar -> Timeout -- ^ The session timeout (milliseconds) -> Maybe Watcher -- ^ The global watcher function. When notifications are -- triggered this function will be invoked -> Maybe ClientID -- ^ The id of a previously established session that -- this client will be reconnecting to -> (Zookeeper -> IO a) -- ^ The main loop. The session is terminated when this -- function exists (successfully or not) -> IO a withZookeeper endpoint timeout watcher clientId io = do withCString endpoint $ \strPtr -> mask $ \restore -> do cWatcher <- wrapWatcher watcher zh <- throwIfNull "zookeeper_init" $ c_zookeeperInit strPtr cWatcher (fromIntegral timeout) cClientIdPtr nullPtr 0 value <- (restore $ io (Zookeeper zh)) `onException` c_zookeeperClose zh c_zookeeperClose zh return value where cClientIdPtr = case clientId of Nothing -> nullPtr Just (ClientID ptr) -> ptr -- | Sets [or redefines] the watcher function setWatcher :: Zookeeper -- ^ Zookeeper handle -> Watcher -- ^ New watch function to register -> IO () setWatcher (Zookeeper zptr) watcher = c_zooSetWatcher zptr =<< wrapWatcher (Just watcher) -- | The current state of this session getState :: Zookeeper -- ^ Zookeeper handle -> IO State -- ^ Current state getState (Zookeeper zh) = fmap toState $ c_zooState zh -- | The client session id, only valid if the session currently -- connected [ConnectedState] getClientId :: Zookeeper -> IO ClientID getClientId (Zookeeper zh) = fmap ClientID $ c_zooClientId zh -- | The timeout for this session, only valid if the session is -- currently connected [ConnectedState] getRecvTimeout :: Zookeeper -> IO Int getRecvTimeout (Zookeeper zh) = fmap fromIntegral $ c_zooRecvTimeout zh -- | Sets the debugging level for the c-library setDebugLevel :: ZLogLevel -> IO () setDebugLevel = c_zooSetDebugLevel . fromLogLevel -- | Creates a znode (asynchornous) create :: Zookeeper -- ^ Zookeeper handle -> String -- ^ The name of the znode expressed as a file name with slashes -- separating ancestors of the znode -> Maybe B.ByteString -- ^ The data to be stored in the znode -> AclList -- ^ The initial ACL of the node. The ACL must not be empty -> [CreateFlag] -- ^ Optional, may be empty -> (Either ZKError String -> IO ()) -- ^ The callback function. On error the user may observe the -- following values: -- -- * Left NoNodeError -> the parent znode does not exit -- -- * Left NodeExistsError -> the node already exists -- -- * Left NoAuthError -> client does not have permission -- -- * Left NoChildrenForEphemeralsError -> cannot create children of ephemeral nodes -- -- * Left BadArgumentsError -> invalid input params -- -- * Left InvalidStateError -> Zookeeper state is either `ExpiredSessionState' or `AuthFailedState' -> IO () create (Zookeeper zh) path mvalue acls flags callback = withCString path $ \pathPtr -> maybeUseAsCStringLen mvalue $ \(valuePtr, valueLen) -> withAclList acls $ \aclPtr -> do cStrFn <- wrapStringCompletion callback rc <- c_zooACreate zh pathPtr valuePtr (fromIntegral valueLen) aclPtr (fromCreateFlags flags) cStrFn nullPtr when (not $ isZOK rc) (callback $ Left (toZKError rc)) where maybeUseAsCStringLen Nothing f = f (nullPtr, -1) maybeUseAsCStringLen (Just s) f = B.useAsCStringLen s f -- | Delete a znode in zookeeper delete :: Zookeeper -- ^ Zookeeper handle -> String -- ^ The name of the znode expressed as a file name with slashes -- separating ancestors of the znode -> Maybe Version -- ^ The expected version of the znode. The function will fail -- if the actual version of the znode does not match the -- expected version. If `Nothing' is given the version check -- will not take place -> IO (Either ZKError ()) -- ^ If an error occurs, you may observe the following values: -- * Left NoNodeError -> znode does not exist -- -- * Left NoAuthError -> client does not have permission -- -- * Left BadVersionError -> expected version does not match actual version -- -- * Left BadArgumentsError -> invalid input parameters -- -- * Left InvalidStateError -> Zookeeper state is either `ExpiredSessionState' or `AuthFailedState' delete (Zookeeper zh) path mversion = withCString path $ \pathPtr -> tryZ (c_zooDelete zh pathPtr (maybe (-1) fromIntegral mversion)) (return ()) -- ^ Checks the existence of a znode exists :: Zookeeper -- ^ Zookeeper handle -> String -- ^ The name of the znode expressed as a file name with slashes -- separating ancestors of the znode -> Maybe Watcher -- ^ This is set even if the znode does not exist. This allows -- users to watch znodes to appear -> IO (Either ZKError Stat) -- ^ If an error occurs, you may observe the following values: -- * Left NoNodeError -> znode does not exist -- -- * Left NoAuthError -> client does not have permission -- -- * Left BadArgumentsError -> invalid input parameters -- -- * Left InvalidStateError -> Zookeeper state is either `ExpiredSessionState' or `AuthFailedState' exists (Zookeeper zh) path mwatcher = withCString path $ \pathPtr -> allocaStat $ \statPtr -> do cWatcher <- wrapWatcher mwatcher tryZ (c_zooWExists zh pathPtr cWatcher nullPtr statPtr) (toStat statPtr) -- | Lists the children of a znode (asynchronous) getChildren :: Zookeeper -- ^ Zookeeper handle -> String -- ^ The name of the znode expressed as a file name with slashes -- separating ancestors of the znode -> Maybe Watcher -- ^ The watch to be set at the server to notify the user -- if the node changes -> (Either ZKError [String] -> IO ()) -- ^ The callback function -> IO () getChildren (Zookeeper zh) path mwatcher callback = do withCString path (\pathPtr -> do cWatcher <- wrapWatcher mwatcher cStrFn <- wrapStringsCompletion callback rc <- c_zooAWGetChildren zh pathPtr cWatcher nullPtr cStrFn nullPtr when (not $ isZOK rc) (callback $ Left (toZKError rc))) -- | Gets the data associated with a znode get :: Zookeeper -- ^ The Zookeeper handle -> String -- ^ The name of the znode expressed as a file name with slashes -- separating ancestors of the znode -> Maybe Watcher -- ^ When provided, a watch will be set at the server to notify -- the client if the node changes -> (Either ZKError (Maybe B.ByteString, Stat) -> IO ()) -- ^ The callback function -> IO () get (Zookeeper zh) path mwatcher callback = withCString path $ \pathPtr -> do cWatcher <- wrapWatcher mwatcher cDataFn <- wrapDataCompletion callback rc <- c_zooAWGet zh pathPtr cWatcher nullPtr cDataFn nullPtr when (not $ isZOK rc) (callback $ Left (toZKError rc)) -- | Sets the data associated with a znode set :: Zookeeper -- ^ Zookeeper handle -> String -- ^ The name of the znode expressed as a file name with slashes -- separating ancestors of the znode -> Maybe B.ByteString -- ^ The data to set on this znode -> Maybe Version -- ^ The expected version of the znode. The function will fail -- if the actual version of the znode does not match the -- expected version. If `Nothing' is given the version check -- will not take place -> IO (Either ZKError Stat) set (Zookeeper zh) path mdata version = withCString path $ \pathPtr -> allocaStat $ \statPtr -> do maybeUseAsCStringLen mdata $ \(dataPtr, dataLen) -> do rc <- c_zooSet2 zh pathPtr dataPtr (fromIntegral dataLen) (maybe (-1) fromIntegral version) statPtr onZOK rc (toStat statPtr) where maybeUseAsCStringLen Nothing f = f (nullPtr, -1) maybeUseAsCStringLen (Just s) f = B.useAsCStringLen s f -- | Sets the acl associated with a node. This operation is not -- recursive on the children. See 'getAcl' for more information. setAcl :: Zookeeper -- ^ Zookeeper handle -> String -- ^ The name of the znode expressed as a file name with slashes -- separating ancestors of the znode -> Maybe Version -- ^ The expected version of the znode. The function will fail -- if the actual version of the znode does not match the -- expected version. If `Nothing' is given the version check -- will not take place -> AclList -- ^ The ACL list to be set on the znode. The ACL must not be empty -> IO (Either ZKError ()) setAcl (Zookeeper zh) path version acls = withCString path $ \pathPtr -> withAclList acls $ \aclPtr -> do rc <- c_zooSetAcl zh pathPtr (maybe (-1) fromIntegral version) aclPtr onZOK rc $ return () -- | Gets the acl associated with a node. Unexpectedly, 'setAcl' and -- 'getAcl' are not symmetric: -- -- > setAcl zh path Nothing OpenAclUnsafe -- > getAcl zh path (..) -- yields AclList instead of OpenAclUnsafe getAcl :: Zookeeper -- ^ The zookeeper handle -> String -- ^ The name of the znode expressed as a file name with slashes -- separating ancestors of the znode -> (Either ZKError (AclList, Stat) -> IO ()) -- ^ The callback function -> IO () getAcl (Zookeeper zh) path callback = withCString path $ \pathPtr -> do cAclFn <- wrapAclCompletion callback rc <- c_zooAGetAcl zh pathPtr cAclFn nullPtr when (not $ isZOK rc) (callback $ Left (toZKError rc)) -- | Specify application credentials -- -- The application calls this function to specify its credentials for -- purposes of authentication. The server will use the security -- provider specified by the scheme parameter to authenticate the -- client connection. If the authentication request has failed: -- -- * the server connection is dropped; -- -- * the watcher is called witht AuthFailedState value as the state -- parameter; addAuth :: Zookeeper -- ^ Zookeeper handle -> Scheme -- ^ Scheme id of the authentication scheme. Natively supported: -- -- * "digest" -> password authentication; -- -- * "ip" -> client's IP address; -- -- * "host" -> client's hostname; -> B.ByteString -- ^ Applicaton credentials. The actual value depends on the scheme -> (Either ZKError () -> IO ()) -- ^ The callback function -> IO () addAuth (Zookeeper zh) scheme cert callback = withCString scheme $ \schemePtr -> B.useAsCStringLen cert $ \(certPtr, certLen) -> do cVoidFn <- wrapVoidCompletion callback rc <- c_zooAddAuth zh schemePtr certPtr (fromIntegral certLen) cVoidFn nullPtr when (not $ isZOK rc) (callback $ Left (toZKError rc)) -- $description -- -- This library provides haskell bindings for zookeeper c-library. The -- underlying library exposes two classes of functions: synchronous -- and asynchronous. Whenever possible the synchronous functions are -- used. -- -- The reason we do not always use the synchronous version is that it -- requires the caller to allocate memory and currently it is -- impossible to know (at least I could not figure it) how much memory -- should be allocated. The asynchronous version has no such problem -- as it manages memory internally. -- $example -- -- The following snippet creates a `/foobar' znode, then it lists and -- prints all children of the `/' znode: -- -- > module Main where -- > -- > import Database.Zookeeper -- > import Control.Concurrent -- > -- > main :: IO () -- > main = do -- > mvar <- newEmptyMVar -- > withZookeeper "localhost:2181" 1000 (Just $ watcher mvar) Nothing $ \_ -> do -- > takeMVar mvar >>= print -- > where -- > watcher mvar zh _ ConnectedState _ = -- > create zh "/foobar" Nothing OpenAclUnsafe [] $ \_ -> -- > getChildren zh "/" Nothing (putMVar mvar) -- $notes -- * Watcher callbacks must never block; -- -- * Make sure you link against zookeeper_mt; -- -- * Make sure you are using the `threaded' (GHC) runtime; -- -- * The connection is closed right before the 'withZookeeper' -- terminates; -- -- * There is no yet support for multi operations (executing a -- series of operations atomically);