View Source ToxiproxyEx (toxiproxy_ex v2.0.0)

ToxiproxyEx is an Elixir API client for the resilience testing tool Toxiproxy.

Toxiproxy is a proxy to simulate network and system conditions. The Elixir API aims to make it simple to write tests that ensure your application behaves appropriately under harsh conditions. Before you can use the Elixir library, you need to read the Usage section of the Toxiproxy README.

Usage

By default the Elixir client communicates with the Toxiproxy daemon via HTTP on http://127.0.0.1:8474, but you can point to any host via your application configuration:

config :toxiproxy_ex, host: "http://toxiproxy.local:8844"

For example, to simulate 1000ms latency on a database server you can use the latency toxic with the latency argument (see the Toxiproxy project for a list of all toxics):

ToxiproxyEx.get!(:mysql_master)
|> ToxiproxyEx.toxic(:latency, latency: 1000)
|> ToxiproxyEx.apply!(fn ->
  Repo.all(Shop) # this took at least 1s
end)

You can also take an endpoint down for the duration of a function at the TCP level:

ToxiproxyEx.get!(:mysql_master)
|> ToxiproxyEx.down!(fn ->
  Repo.all(Shop) # this'll raise
end)

If you want to simulate all your Redis instances being down:

ToxiproxyEx.grep!(~r/redis/)
|> ToxiproxyEx.down!(fn ->
  # any redis call will fail
end)

If you want to simulate that your cache server is slow at incoming network (upstream), but fast at outgoing (downstream), you can apply a toxic to just the upstream:

ToxiproxyEx.get!(:cache)
|> ToxiproxyEx.upstream(:latency, latency: 1000)
|> ToxiproxyEx.apply!(fn ->
  Cache.get(:omg) # will take at least a second
end)

By default the toxic is applied to the downstream connection, you can be explicit and compose them:

ToxiproxyEx.grep!(~r/redis/)
|> ToxiproxyEx.upstream(:slow_close, delay: 100)
|> ToxiproxyEx.downstream(:latency, jitter: 300)
|> ToxiproxyEx.apply!(fn ->
  # all redises are now slow at responding and closing
end)

See the Toxiproxy README for a list of toxics.

Populate

To populate Toxiproxy pass the proxy configurations to ToxiproxyEx.populate!:

ToxiproxyEx.populate!([
  %{
    name: "mysql_master",
    listen: "localhost:21212",
    upstream: "localhost:3306",
  },
  %{
    name: "mysql_read_only",
    listen: "localhost:21213",
    upstream: "localhost:3306",
  }
])

This will create the proxies passed, or replace the proxies if they already exist in Toxiproxy. It's recommended to do this as early in your application startup process as possible, see the Toxiproxy README. If you have many proxies, we recommend storing the Toxiproxy configs in a configuration file and deserializing it into ToxiproxyEx.populate!/1.

Error Handling

This library made the choice to use exceptions on the public API methods to signal errors.

This was chosen since this is a library meant to be used in testing code only, where you want test cases to fail if your set assumptions are not met. In this sense setting assumptions that will not be met (toxiproxy-server is not running, passing invalid configurations) is considered to be a developer error and should be fixed rather than handled in code.

Server Errors

If any API interaction with toxiproxy fails, a ServerError will be raised.

Client Errors

If you miss-configure toxiproxy via the elixir API, an ArgumentError will be raised.

Summary

Types

A hostname or IP address including a port number, e.g. localhost:4539.

A proxy that intercepts traffic to and from an upstream server.

A map containing fields required to setup a proxy. Designed to be used with ToxiproxyEx.populate!/1.

A collection of proxies.

Functions

Retrieves a list of all proxies from the toxiproxy server.

Applies all toxics previously defined on the list of proxies during the duration of the given function.

Creates a proxy on the toxiproxy server.

Deletes one or multiple proxies on the toxiproxy server.

Takes down the proxy or the list of proxies during the duration of the given function.

Adds an downstream toxic to the proxy or list of proxies that will be enabled when passed to ToxiproxyEx.apply!/2.

Retrieves a proxy from the toxiproxy server.

Retrieves a list of proxies from the toxiproxy server where the name matches the specificed regex.

Creates proxies based on the passed data. This is usefull to quickly create multiple proxies based on hardcoded value or values read from external sources such as a config file.

Re-enables are proxies and disables all toxics on toxiproxy.

Adds an upstream toxic to the proxy or list of proxies that will be enabled when passed to ToxiproxyEx.apply!/2.

Gets the version of the running toxiproxy server.

Types

@type host_with_port() :: String.t()

A hostname or IP address including a port number, e.g. localhost:4539.

@opaque proxy()

A proxy that intercepts traffic to and from an upstream server.

@type proxy_map() :: %{
  :name => String.t(),
  :upstream => host_with_port(),
  optional(:listen) => host_with_port(),
  optional(:enabled) => true | false
}

A map containing fields required to setup a proxy. Designed to be used with ToxiproxyEx.populate!/1.

Link to this opaque

toxic_collection()

View Source (opaque)
@opaque toxic_collection()

A collection of proxies.

Functions

@spec all!() :: toxic_collection()

Retrieves a list of all proxies from the toxiproxy server.

Raises ToxiproxyEx.ServerError if the list of proxies could not be retrieved.

Examples

Retrievs a proxy:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> ToxiproxyEx.create!(upstream: "localhost:3307", name: "test_redis_master")
iex> ToxiproxyEx.all!()
Link to this function

apply!(toxic_collection, fun)

View Source
@spec apply!(toxic_collection(), (-> result)) :: result when result: var

Applies all toxics previously defined on the list of proxies during the duration of the given function.

Raises ToxiproxyEx.ServerError if the toxics could not be enabled and disabled again on the server.

Examples

Add toxics and apply them toxic to a single proxy:

iex> proxy = ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> proxies = ToxiproxyEx.downstream(proxy, :slow_close, delay: 100)
iex> proxies = ToxiproxyEx.downstream(proxies, :latency, jitter: 300)
iex> ToxiproxyEx.apply!(proxies, fn ->
...>  # All calls to mysql master are now slow at responding and closing.
...>  nil
...> end)

Add toxics and apply them toxic to a list of proxies:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_follower")
iex> proxies = ToxiproxyEx.all!()
iex> proxies = ToxiproxyEx.downstream(proxies, :slow_close, delay: 100)
iex> proxies = ToxiproxyEx.downstream(proxies, :latency, jitter: 300)
iex> ToxiproxyEx.apply!(proxies, fn ->
...>  # All calls to mysql master and follower are now slow at responding and closing.
...>  nil
...> end)
@spec create!(
  upstream: host_with_port(),
  name: String.t() | atom(),
  listen: host_with_port() | nil,
  enabled: true | false | nil
) :: proxy()

Creates a proxy on the toxiproxy server.

Raises ToxiproxyEx.ServerError if the creation fails.

Examples

Create a new proxy:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")

Create a new proxy that listens on a specific port:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", listen: "localhost:5555", name: "test_mysql_master")

Create a new proxy that is disabled by default:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master", enabled: false)
@spec destroy!(proxy() | toxic_collection()) :: :ok

Deletes one or multiple proxies on the toxiproxy server.

Raises ToxiproxyEx.ServerError if the deletion fails.

Examples

Destroy a single proxy:

iex> ToxiproxyEx.create!(upstream: "localhost:3456", name: :test_mysql_master)
iex> proxy = ToxiproxyEx.get!(:test_mysql_master)
iex> ToxiproxyEx.destroy!(proxy)
:ok

Destroy all proxies:

iex> ToxiproxyEx.create!(upstream: "localhost:3456", name: :test_mysql_master)
iex> proxies = ToxiproxyEx.all!()
iex> ToxiproxyEx.destroy!(proxies)
:ok
Link to this function

down!(proxy_or_collection, fun)

View Source
@spec down!(toxic_collection() | proxy(), (-> result)) :: result when result: var

Takes down the proxy or the list of proxies during the duration of the given function.

Raises ToxiproxyEx.ServerError if the proxy or list of proxies could not have been disabled and enabled again on the server.

Examples

Take down a single proxy:

iex> proxy = ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> ToxiproxyEx.down!(proxy, fn ->
...>  # Takes mysql master down.
...>  nil
...> end)

Take down a list of proxies:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_follower")
iex> proxies = ToxiproxyEx.all!()
iex> ToxiproxyEx.down!(proxies, fn ->
...>  # Takes mysql master and follower down.
...>  nil
...> end)
Link to this function

downstream(proxy_or_collection, type, attrs \\ [])

View Source
@spec downstream(proxy() | toxic_collection(), atom(), []) :: toxic_collection()

Adds an downstream toxic to the proxy or list of proxies that will be enabled when passed to ToxiproxyEx.apply!/2.

Examples

Add an downstream toxic to a proxy:

iex> proxy = ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> proxies = ToxiproxyEx.downstream(proxy, :latency, latency: 1000)
iex> ToxiproxyEx.apply!(proxies, fn ->
...>  # Do some testing
...>  nil
...> end)

Add an downstream toxic to a list of proxies:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> ToxiproxyEx.create!(upstream: "localhost:3307", name: "test_mysql_follower")
iex> proxies = ToxiproxyEx.all!()
iex> proxies = ToxiproxyEx.downstream(proxies, :latency, latency: 1000)
iex> ToxiproxyEx.apply!(proxies, fn ->
...>  # Do some testing
...>  nil
...> end)
@spec get!(atom() | String.t()) :: proxy()

Retrieves a proxy from the toxiproxy server.

Raises ToxiproxyEx.ServerError if the proxy could not be retrieved. Raises ArgumentError if the proxy does not exist.

Examples

Retrievs a proxy:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> ToxiproxyEx.get!(:test_mysql_master)
@spec grep!(Regex.t()) :: toxic_collection()

Retrieves a list of proxies from the toxiproxy server where the name matches the specificed regex.

Raises ToxiproxyEx.ServerError if the list of proxies could not be retrieved. Raises ArgumentError if no proxy matching the specified regex does exist.

Examples

Retrievs a proxy:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> ToxiproxyEx.create!(upstream: "localhost:3307", name: "test_mysql_follower")
iex> ToxiproxyEx.create!(upstream: "localhost:3308", name: "test_redis_master")
iex> ToxiproxyEx.grep!(~r/master/)
@spec populate!([proxy_map()]) :: toxic_collection()

Creates proxies based on the passed data. This is usefull to quickly create multiple proxies based on hardcoded value or values read from external sources such as a config file.

Nonexisting proxies will be created and existing ones will be updated to match the passed data.

Raises ToxiproxyEx.ServerError if the proxies could not have been created on the server.

Examples

Creating proxies:

iex> ToxiproxyEx.populate!([
...>  %{name: "test_mysql_master", upstream: "localhost:5765"},
...>  %{name: "test_mysql_follower", upstream: "localhost:5766", enabled: false}
...> ])
@spec reset!() :: :ok

Re-enables are proxies and disables all toxics on toxiproxy.

Raises ToxiproxyEx.ServerError if the server could not have been reset.

Examples

Reset toxiproxy:

iex> ToxiproxyEx.reset!()
:ok
Link to this function

toxic(proxy_or_collection, type, attrs \\ [])

View Source
@spec toxic(proxy() | toxic_collection(), atom(), []) :: toxic_collection()

Alias for ToxiproxyEx.downstream/3.

Link to this function

toxicate(proxy_or_collection, type, attrs \\ [])

View Source
@spec toxicate(proxy() | toxic_collection(), atom(), []) :: toxic_collection()

Alias for ToxiproxyEx.downstream/3.

Link to this function

upstream(proxy_or_collection, type, attrs \\ [])

View Source
@spec upstream(proxy() | toxic_collection(), atom(), []) :: toxic_collection()

Adds an upstream toxic to the proxy or list of proxies that will be enabled when passed to ToxiproxyEx.apply!/2.

Examples

Add an upstream toxic to a proxy:

iex> proxy = ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> proxies = ToxiproxyEx.upstream(proxy, :latency, latency: 1000)
iex> ToxiproxyEx.apply!(proxies, fn ->
...>  # Do some testing
...>  nil
...> end)

Add an upstream toxic to a list of proxies:

iex> ToxiproxyEx.create!(upstream: "localhost:3306", name: "test_mysql_master")
iex> ToxiproxyEx.create!(upstream: "localhost:3307", name: "test_mysql_follower")
iex> proxies = ToxiproxyEx.all!()
iex> proxies = ToxiproxyEx.upstream(proxies, :latency, latency: 1000)
iex> ToxiproxyEx.apply!(proxies, fn ->
...>  # Do some testing
...>  nil
...> end)
@spec version!() :: String.t()

Gets the version of the running toxiproxy server.

Raises ToxiproxyEx.ServerError if the version could not have been fetched from the server.

Examples

Get running toxiproxy version:

iex> ToxiproxyEx.version!()
"2.1.2"