
⏰ 🔥 Um proxy TCP para simular condições de rede e sistema para testes de caos e resiliência
Toxiproxy é um framework para simular condições de rede. Ele foi feito especificamente para funcionar em ambientes de teste, CI e desenvolvimento, oferecendo suporte a adulteração determinística de conexões, mas com suporte a caos aleatorizado e personalização. Toxiproxy é a ferramenta que você precisa para provar com testes que sua aplicação não possui pontos únicos de falha. Temos usado com sucesso em todos os ambientes de desenvolvimento e teste na Shopify desde outubro de 2014. Veja nosso [post no blog][blog] sobre resiliência para mais informações.
O uso do Toxiproxy consiste em duas partes. Um proxy TCP escrito em Go (o que este repositório contém) e um cliente que se comunica com o proxy via HTTP. Você configura sua aplicação para que todas as conexões de teste passem pelo Toxiproxy e então pode manipular a saúde delas via HTTP. Veja Uso abaixo para saber como configurar seu projeto.
Por exemplo, para adicionar 1000ms de latência à resposta do MySQL a partir do cliente Ruby:
Toxiproxy[:mysql_master].downstream(:latency, latency: 1000).apply do
Shop.first # this takes at least 1s
end
Para derrubar todas as instâncias Redis:
Toxiproxy[/redis/].down do
Shop.first # this will throw an exception
end
Embora os exemplos neste README estejam atualmente em Ruby, não há nada que o impeça de criar um cliente em qualquer outra linguagem (veja Clients).
Os existentes que encontramos não forneciam o tipo de API dinâmica que
precisávamos para testes de integração e unitários. Ferramentas Linux como nc
e outras não são multiplataforma e exigem root, o que as torna problemáticas em
ambientes de teste, desenvolvimento e CI.
Vamos percorrer um exemplo com uma aplicação Rails. Observe que o Toxiproxy não está de forma alguma vinculado ao Ruby, foi apenas o nosso primeiro caso de uso. Você pode ver o exemplo completo em sirupsen/toxiproxy-rails-example. Para começar imediatamente, vá direto para Uso.
Para o nosso popular blog, por algum motivo estamos armazenando as tags dos
nossos posts no Redis e os próprios posts no MySQL. Podemos ter uma classe
Post que inclui alguns métodos para manipular tags em um
conjunto Redis:
class Post < ActiveRecord::Base
# Return an Array of all the tags.
def tags
TagRedis.smembers(tag_key)
end
# Add a tag to the post.
def add_tag(tag)
TagRedis.sadd(tag_key, tag)
end
# Remove a tag from the post.
def remove_tag(tag)
TagRedis.srem(tag_key, tag)
end
# Return the key in Redis for the set of tags for the post.
def tag_key
"post:tags:#{self.id}"
end
end
Decidimos que gerar erro ao escrever no armazenamento de dados de tags
(adicionar/remover) é aceitável. No entanto, se o armazenamento de dados de tags estiver fora do ar, devemos conseguir
ver a publicação sem tags. Poderíamos simplesmente capturar o
Redis::CannotConnectError em torno da chamada Redis SMEMBERS no método tags.
Vamos usar o Toxiproxy para testar isso.
Como já instalamos o Toxiproxy e ele está em execução na nossa máquina, podemos
pular para a etapa 2. É aqui que precisamos garantir que o Toxiproxy tenha um mapeamento para
as tags do Redis. Em config/boot.rb (antes de qualquer conexão ser feita) adicionamos:
require 'toxiproxy'
Toxiproxy.populate([
{
name: "toxiproxy_test_redis_tags",
listen: "127.0.0.1:22222",
upstream: "127.0.0.1:6379"
}
])
Em seguida, em config/environments/test.rb, definimos o TagRedis como um cliente Redis que se conecta ao Redis através do Toxiproxy adicionando esta linha:
TagRedis = Redis.new(port: 22222)
Todas as chamadas no ambiente de teste agora passam pelo Toxiproxy. Isso significa que podemos adicionar um teste unitário onde simulamos uma falha:
test "should return empty array when tag redis is down when listing tags" do
@post.add_tag "mammals"
# Take down all Redises in Toxiproxy
Toxiproxy[/redis/].down do
assert_equal [], @post.tags
end
end
O teste falha com Redis::CannotConnectError. Perfeito! O Toxiproxy derrubou o Redis com sucesso durante a duração do bloco. Vamos corrigir o método tags para ser resiliente:
def tags
TagRedis.smembers(tag_key)
rescue Redis::CannotConnectError
[]
end
Os testes passam! Agora temos um teste unitário que prova que a busca das tags quando o Redis está fora do ar retorna um array vazio, em vez de lançar uma exceção. Para cobertura completa, você também deve escrever um teste de integração que envolva a busca da página inteira do post do blog quando o Redis está fora do ar.
A aplicação de exemplo completa está em sirupsen/toxiproxy-rails-example.
Configurar um projeto para usar o Toxiproxy consiste em três etapas:
Linux
Consulte Releases para obter os binários mais recentes
e pacotes de sistema para a sua arquitetura.
Ubuntu
$ wget -O toxiproxy-2.13.0.deb https://github.com/Shopify/toxiproxy/releases/download/v2.13.0/toxiproxy_2.13.0_linux_amd64.deb
$ sudo dpkg -i toxiproxy-2.13.0.deb
$ sudo service toxiproxy start
OS X
Com Homebrew:
$ brew tap shopify/shopify
$ brew install toxiproxy
Ou com MacPorts:
$ port install toxiproxy
Windows