toxiproxy-haskell 0.1.0.0 → 0.2.0.0
raw patch · 3 files changed
+173/−46 lines, 3 files
Files
- src/Toxiproxy.hs +166/−39
- test/Spec.hs +5/−5
- toxiproxy-haskell.cabal +2/−2
src/Toxiproxy.hs view
@@ -1,6 +1,8 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-} module Toxiproxy ( getVersion , postReset@@ -18,56 +20,85 @@ , Proxy(..) , Toxic(..) , Populate(..)+ , Version(..)+ , Stream(..)+ , ToxicType(..) , toxiproxyUrl , withDisabled , withToxic , withProxy , run- , Version ) where import Servant.API import Servant.Client import qualified Data.Proxy as Proxy-import Data.Text (Text)+import Data.Text (Text, pack, toLower, unpack) import Data.List (stripPrefix)-import Data.Char (toLower)+import qualified Data.Char as Char (toLower) import GHC.Generics import Data.Aeson (FromJSON, parseJSON, fieldLabelModifier, defaultOptions, genericParseJSON,- ToJSON, genericToJSON, toJSON)+ ToJSON, genericToJSON, toJSON, FromJSONKey, Value( String )) import Data.Map.Strict (Map) import Network.HTTP.Client (newManager, defaultManagerSettings) import Control.Exception (bracket) import Control.Monad (void)+import Data.String (IsString) type ToxiproxyAPI = "version" :> Get '[PlainText] Version :<|> "reset" :> Post '[] NoContent- :<|> "proxies" :> Get '[JSON] (Map Text Proxy)- :<|> "proxies" :> ReqBody '[JSON] Proxy :> Post '[JSON] Proxy- :<|> "proxies" :> Capture "name" Text :> Get '[JSON] Proxy- :<|> "populate" :> ReqBody '[JSON] [Proxy] :> Post '[JSON] Populate- :<|> "proxies" :> Capture "name" Text :> ReqBody '[JSON] Proxy :> Post '[JSON] Proxy- :<|> "proxies" :> Capture "name" Text :> Delete '[] NoContent- :<|> "proxies" :> Capture "name" Text :>+ :<|> "proxies" :> Get '[JSON] (Map ProxyName Proxy)+ :<|> "proxies" :> ReqBody '[JSON] Proxy :> Post '[JSON] Proxy+ :<|> "proxies" :> Capture "name" ProxyName :> Get '[JSON] Proxy+ :<|> "populate" :> ReqBody '[JSON] [Proxy] :> Post '[JSON] Populate+ :<|> "proxies" :> Capture "name" ProxyName :> ReqBody '[JSON] Proxy :> Post '[JSON] Proxy+ :<|> "proxies" :> Capture "name" ProxyName :> Delete '[] NoContent+ :<|> "proxies" :> Capture "name" ProxyName :> "toxics" :> Get '[JSON] [Toxic]- :<|> "proxies" :> Capture "name" Text :>- "toxics" :> ReqBody '[JSON] Toxic :> Post '[JSON] Toxic- :<|> "proxies" :> Capture "name" Text :>- "toxics" :> Capture "name" Text :> Get '[JSON] Toxic- :<|> "proxies" :> Capture "name" Text :>- "toxics" :> Capture "name" Text :> ReqBody '[JSON] Toxic :> Get '[JSON] Toxic- :<|> "proxies" :> Capture "name" Text :>- "toxics" :> Capture "name" Text :> Delete '[JSON] NoContent+ :<|> "proxies" :> Capture "name" ProxyName :>+ "toxics" :> ReqBody '[JSON] Toxic :> Post '[JSON] Toxic+ :<|> "proxies" :> Capture "name" ProxyName :>+ "toxics" :> Capture "name" ToxicName :> Get '[JSON] Toxic+ :<|> "proxies" :> Capture "name" ProxyName :>+ "toxics" :> Capture "name" ToxicName :> ReqBody '[JSON] Toxic :> Get '[JSON] Toxic+ :<|> "proxies" :> Capture "name" ProxyName :>+ "toxics" :> Capture "name" ToxicName :> Delete '[JSON] NoContent -type Version = Text+-- | A unique string for identifying a proxy on the server.+newtype ProxyName = ProxyName Text+ deriving (Show, Eq, IsString, Ord, Generic, ToHttpApiData, FromJSONKey) +instance FromJSON ProxyName+instance ToJSON ProxyName++-- | A unique string for identifying a toxic on a proxy.+newtype ToxicName = ToxicName Text+ deriving (Show, Eq, IsString, Generic, ToHttpApiData)++instance FromJSON ToxicName+instance ToJSON ToxicName++-- | The version of the Toxiproxy server. This library is fully supported by any version+-- greater or equal to 2.1.3.+newtype Version = Version Text+ deriving (Show, Eq, MimeUnrender PlainText)++-- | A Toxiproxy proxy. It forwards TCP connections between a listen and upstream host.+-- Toxics can be injected into the proxy to simulate network failure. data Proxy = Proxy- { proxyName :: Text- , proxyListen :: Text- , proxyUpstream :: Text+ { proxyName :: ProxyName+ -- ^ A unique human readable name to identify a proxy.+ , proxyListen :: Host+ -- ^ The proxy listens on this host:port.+ , proxyUpstream :: Host+ -- ^ The proxy forwards to this upstream host:port. , proxyEnabled :: Bool+ -- ^ Whether a proxy is currently listening / accepting connections. , proxyToxics :: [Toxic]+ -- ^ The toxics currently applied to the proxy. These should not be specified when+ -- initially creating a proxy. They must be created seperately with 'createToxic'+ -- or 'withToxic'. } deriving (Show, Eq, Generic) instance FromJSON Proxy where@@ -80,12 +111,26 @@ defaultOptions { fieldLabelModifier = stripPrefixJSON "proxy" } +-- | A host:port pair to represent the entrence of a proxy or the upstream the proxy+-- forwards to. For the best experience, provide 127.0.0.1 instead of localhost.+type Host = Text++-- | A toxic is applied to a proxy. It allows the user to simulate a specified kind of+-- network failure on the proxy. data Toxic = Toxic- { toxicName :: Text- , toxicType :: Text- , toxicStream :: Text+ { toxicName :: ToxicName+ -- ^ A unique human readable name to identify a toxic.+ , toxicType :: ToxicType+ -- ^ The type of toxic. For example "latency". Please refer to 'ToxicType' or the+ -- Toxiproxy documentation for more information.+ , toxicStream :: Stream+ -- ^ The direction on which the toxic is applied. Please refer to 'Stream'. , toxicToxicity :: Float+ -- ^ The strength that the toxic is applied to the proxy. Please refer to the Toxiproxy+ -- documation. , toxicAttributes :: Map Text Int+ -- ^ Attributes configure a toxic. They differ based on the 'ToxicType'. Please refer to+ -- the Toxiproxy documentation. } deriving (Show, Eq, Generic) instance FromJSON Toxic where@@ -98,6 +143,7 @@ defaultOptions { fieldLabelModifier = stripPrefixJSON "toxic" } +-- | The return value of the 'populate' endpoint. newtype Populate = Populate { populateProxies :: [Proxy] } deriving (Show, Eq, Generic) @@ -106,41 +152,108 @@ defaultOptions { fieldLabelModifier = stripPrefixJSON "populate" } +-- | A toxic can be applied to the upstream or the downstream of a connection. Upstream is+-- the stream traveling from the connecting client to the upstream server. Downstream is+-- the stream traveling from the upstream server to the connecting client.+data Stream = Upstream | Downstream+ deriving (Show, Eq)++instance ToJSON Stream where+ toJSON Upstream = String "upstream"+ toJSON Downstream = String "downstream"++instance FromJSON Stream where+ parseJSON (String stream) =+ case stream of+ "upstream" -> return Upstream+ "downstream" -> return Downstream++-- | Different toxic types simulate different kinds of failure. Different toxics require+-- different attribute configuration. Please refer to the Toxiproxy documentation.+data ToxicType =+ Latency+ | Bandwidth+ | SlowClose+ | Timeout+ | Slicer+ | LimitData+ | Other Text+ deriving (Show, Eq)++instance ToJSON ToxicType where+ toJSON Latency = String "latency"+ toJSON Bandwidth = String "bandwidth"+ toJSON SlowClose = String "slow_close"+ toJSON Timeout = String "timeout"+ toJSON Slicer = String "slicer"+ toJSON LimitData = String "limit_data"+ toJSON (Other other) = String other++instance FromJSON ToxicType where+ parseJSON (String toxicType) =+ case toxicType of+ "latency" -> return Latency+ "bandwidth" -> return Bandwidth+ "slow_clos" -> return SlowClose+ "timeout" -> return Timeout+ "slicer" -> return Slicer+ "limit_dat" -> return LimitData+ other -> return . Other $ other+ stripPrefixJSON :: String -> String -> String stripPrefixJSON prefix str = case stripPrefix prefix str of Nothing -> str- Just (first : rest) -> toLower first : rest+ Just (first : rest) -> Char.toLower first : rest toxiproxyAPI :: Proxy.Proxy ToxiproxyAPI toxiproxyAPI = Proxy.Proxy getVersion :: ClientM Version postReset :: ClientM NoContent-getProxies :: ClientM (Map Text Proxy)+getProxies :: ClientM (Map ProxyName Proxy) createProxy :: Proxy -> ClientM Proxy-getProxy :: Text -> ClientM Proxy+getProxy :: ProxyName -> ClientM Proxy postPopulate :: [Proxy] -> ClientM Populate-updateProxy :: Text -> Proxy -> ClientM Proxy-deleteProxy :: Text -> ClientM NoContent-getToxics :: Text -> ClientM [Toxic]-createToxic :: Text -> Toxic -> ClientM Toxic-getToxic :: Text -> Text -> ClientM Toxic-updateToxic :: Text -> Text -> Toxic -> ClientM Toxic-deleteToxic :: Text -> Text -> ClientM NoContent+updateProxy :: ProxyName -> Proxy -> ClientM Proxy+deleteProxy :: ProxyName -> ClientM NoContent+getToxics :: ProxyName -> ClientM [Toxic]+createToxic :: ProxyName -> Toxic -> ClientM Toxic+getToxic :: ProxyName -> ToxicName -> ClientM Toxic+updateToxic :: ProxyName -> ToxicName -> Toxic -> ClientM Toxic+deleteToxic :: ProxyName -> ToxicName -> ClientM NoContent (getVersion :<|> postReset :<|> getProxies :<|> createProxy :<|> getProxy :<|> postPopulate :<|> updateProxy :<|> deleteProxy :<|> getToxics :<|> createToxic :<|> getToxic :<|> updateToxic :<|> deleteToxic) = client toxiproxyAPI +-- | The default Toxiproxy service URL.+-- (127.0.0.1:8474) toxiproxyUrl :: BaseUrl toxiproxyUrl = BaseUrl Http "127.0.0.1" 8474 "" +-- | A helper for easily querying the Toxiproxy API. Assumes Toxiproxy is running on+-- 'toxiproxyUrl'.+--+-- @+-- proxies <- run getProxies+-- @ run :: ClientM a -> IO (Either ServantError a) run f = do manager <- newManager defaultManagerSettings runClientM f (ClientEnv manager toxiproxyUrl) +-- | Given an enabled proxy, disable the proxy, run the given action and then re-enable+-- the proxy.+--+-- This is useful for simulating a crashed server or closed connection.+--+-- @+-- connectToMyProxy -- This will connect.+-- withDisabled myProxy $+-- connectToMyProxy -- This will get rejected.+-- connectToMyProxy -- This will connect again.+-- @ withDisabled :: Proxy -> IO a -> IO a withDisabled proxy f = bracket disable enable $ const f@@ -149,6 +262,15 @@ disable = void . run $ updateProxy (proxyName proxy) disabledProxy disabledProxy = proxy { proxyEnabled = False } +-- | Given a proxy and a toxic, create the toxic on the proxy, run the given action and+-- then delete the toxic.+--+-- This is useful for running some action with a toxic enabled.+--+-- @+-- withToxic myProxy latencyToxic $+-- sendRequestThroughProxy -- This request will have latency applied to it.+-- @ withToxic :: Proxy -> Toxic -> IO a -> IO a withToxic proxy toxic f = bracket enable disable $ const f@@ -156,6 +278,11 @@ enable = void . run $ createToxic (proxyName proxy) toxic disable = const . run $ deleteToxic (proxyName proxy) (toxicName toxic) +-- | Given a proxy record, create the proxy on the server, run the given action and then+-- delete the proxy off the server.+--+-- This is useful for wrapping 'withDisabled' and 'withToxic' calls. It enures that your+-- test cleans up the Toxiproxy server so that proxies don't leak into your other tests. withProxy :: Proxy -> (Proxy -> IO a) -> IO a withProxy proxy = bracket create delete
test/Spec.hs view
@@ -64,8 +64,8 @@ let toxicName = "latency" let toxic = Toxic { toxicName = toxicName- , toxicType = toxicName- , toxicStream = "upstream"+ , toxicType = Latency+ , toxicStream = Upstream , toxicToxicity = 1 , toxicAttributes = Map.fromList [("latency", 1000), ("jitter", 0)] }@@ -113,8 +113,8 @@ } let toxic = Toxic { toxicName = "latency"- , toxicType = "latency"- , toxicStream = "upstream"+ , toxicType = Latency+ , toxicStream = Upstream , toxicToxicity = 1 , toxicAttributes = Map.fromList [("latency", 1000), ("jitter", 0)] }@@ -136,7 +136,7 @@ server = proc "toxiproxy-server" [] version :: Version-version = "git-fe6bf4f"+version = Version "git-fe6bf4f" proxyUrl :: BaseUrl proxyUrl = BaseUrl Http "127.0.0.1" 4444 ""
toxiproxy-haskell.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 5cb73d2ae60bbc8465572da4b0035b5ad27b03f3c50e79f4af4eae94ab5783f3+-- hash: d0e71416ad274526a7e079fcae1e0d802defc1f7a0e5e9e03dcfd872153b1dd2 name: toxiproxy-haskell-version: 0.1.0.0+version: 0.2.0.0 synopsis: Client library for Toxiproxy: a TCP failure testing proxy. description: Please see the README on Github at <https://github.com/jpittis/toxiproxy-haskell#readme> category: web