$30 off During Our Annual Pro Sale. View Details »

Test de infraestructura con pytest - PyConES 2016

Test de infraestructura con pytest - PyConES 2016

Transarencias de mi charla de la PyConES 2016 en Almeria.

Vídeo en https://www.youtube.com/watch?v=l1KfrMFD4iQ

Agustín Herranz

October 08, 2016
Tweet

More Decks by Agustín Herranz

Other Decks in Programming

Transcript

  1. Beneficios de testear • Seguridad / tranquilidad. • Una batería

    de tests es un checkist. • Automatización -> más velocidad. • Documentacion. • Detección de cambios indeseados.
  2. Pytest: Introducción • El mejor 'framework' de test para Python.

    • “Te ayuda a escribir mejores programas” • Usa 'asserts' de Python, nada de historias de xUnit (pero las acepta) Repo: https://github.com/pytest-dev/pytest Docu.: http://docs.pytest.org/en/latest/ Instalar: $ pip install pytest Lanzar: $ pytest | py.test
  3. Pytest: Comenzando import requests def test_blog_landing_page_ok(): r = requests.get('http://agus.tinproject.es') assert

    r.status_code == 200 def blog_article_page_ok_test(): r = requests.get('http://agus.tinproject.es/' \ '2013/02/14/entendiendo-gescal/') assert r.status_code == 200 def test__category_permalinks__ok(): r = requests.get('http://agus.tinproject.es/category/gis/') assert r.status_code == 200 def calendar_month_permalinks__ok__test(): r = requests.get('http://agus.tinproject.es/2010/12/') assert r.status_code == 200
  4. Parametrizando import pytest import requests @pytest.mark.parametrize("url, status", [ ("agus.tinproject.es", 200),

    ("agus.tinproject.es/2013/02/14/entendiendo-gescal/", 200), ("agus.tinproject.es/category/gis", 200), ("agus.tinproject.es/2010/12/", 200), ("agus.tinproject.es/admin", 200), ]) def test__blog_urls(url, status): r = requests.get('http://{}'.format(url)) assert r.status_code == status
  5. ¡Fixtures! @pytest.fixture() def blog(): class Client: def __init__(self): self.scheme =

    "http" self.host = "agus.tinproject.es" def get(self, location, **kwargs): url = "{}://{}{}".format(self.scheme, self.host, location) return requests.get(url, allow_redirects=False, **kwargs) return Client() @pytest.mark.parametrize("endpoint, status", [ ("/", 200), ("/category/gis", 301), ("/admin", 302), ]) def test__blog_endpoints(endpoint, status, blog): r = blog.get(endpoint) assert r.status_code == status
  6. Presentando Sentry • Plataforma de registro de errores y agregación,

    on-line, pero de código abierto y con versión on-premise. http://sentry.io • Hecho en Python con Django. • Vagrant + Debian + Ansible + Pytest/Testinfra • Necesitamos: – python 2.7 – dependencias para librerías python – postgresql – redis – nginx (frontal, no requerido)
  7. Testinfra • Plugin de pytest que nos provee de fixtures

    para testear servidores (Unix/Linux). • “aims to be a Serverspec equivalent in python” • Diferentes formas de conexión. Repo: https://github.com/philpep/testinfra/ Docu.: http://testinfra.readthedocs.io Instalar: $ pip install testinfra Lanzar: $ testinfra <args>
  8. Testinfra: connection backends Conexiones al sujeto (servidor/contenedor) bajo test: •

    docker • salt • ansible • local • paramiko • ssh
  9. Invocando a Testinfra Argumentos para lanzar testinfra: • --connection=connection_backend •

    --hosts=servidor1,servidor2 • --ssh-config=/ruta/a/ssh_config • --sudo | --sudo-user=usuario • --ansible-inventory=/ruta/a/inventario_ansible $ vagrant ssh-config > .vagrant/ssh-config $ testinfra --connection=ssh --hosts=default \ --ssh-config=.vagrant/ssh-config
  10. Testinfra: Modules • Command • LocalCommand • TestinfraBackend • User

    • Group • Sudo • File • Package • Service • Process • Interface • Socket • SystemInfo • Sysctl • MountPoint • Supervisor • Salt • Ansible • PuppetResource • Facter
  11. Testinfra: (Local)Command • Ejecuta un comando en la shell remota.

    • LocalCommand, lo mismo en local. def test__echo(Command): cmd = Command("echo 'Hola PyConES'") assert cmd.rc == 0 assert "PyConES" in cmd.stdout assert cmd.stderr == "" def test__echo_output(Command): output = Command.check_output("echo 'Hola PyConES'") assert "Hola" in output def test__echo_return_code(LocalCommand): cmd = LocalCommand.run_test("echo 'Hola PyConES'") assert cmd.rc == 0
  12. Testinfra: TestinfraBackend • Da información sobre el connection_backend • Permite

    obtener modules de un backend específico. @pytest.mark.skip(reason="This won't work with vagrant because how handle hostnames") def test__webserver_working(TestinfraBackend): host = TestinfraBackend.get_hostname() r = requests.get("http://" + host) assert r.status_code == 200
  13. Testinfra: User • exists • name • uid • gid

    / gids • group / groups • home • shell • password • expiration_date def test__sentry_user(User): sentry = User("sentry") assert sentry.exists assert sentry.name == "sentry" assert sentry.uid < 1000 assert sentry.group == "sentry" assert sentry.shell == "/sbin/nologin"
  14. Testinfra: Sudo • Hacerse pasar por otros usuarios. • No

    funciona si nuestro usuario de conexión no está correctamente configurado (sudoers). def test__sentry_database_exists(Sudo, Command): with Sudo("postgres"): cmd_str = """psql -d sentry -c "SELECT 1;" """ cmd = Command(cmd_str) assert cmd.rc == 0
  15. Testinfra: File • exists • is_file | is_directory • is_pipe

    • is_socket • is_symlink | linked_to • user | uid • group | gid • mode • contains • md5sum | sha256sum • content | content_string • mtime • size @pytest.mark.parametrize("cfg_file", [ "config.yml", "sentry.conf.py", ]) def test__sentry_config__files(File, cfg_file): f = File("/etc/sentry/%s" % cfg_file) assert f.is_file assert f.user == "root" assert f.group == "sentry" assert f.mode == 0o640 @pytest.mark.parametrize("txt", [ "SENTRY_WEB_HOST = '127.0.0.1'", "SENTRY_WEB_PORT = 9000", ]) def test__sentry_config_values(txt, File, Sudo): with Sudo("sentry"): f = File("/etc/sentry/sentry.conf.py") assert f.contains(txt)
  16. Testinfra: Package • is_installed • version @pytest.mark.parametrize("pkg", [ "python-setuptools", "python-pip",

    "python-dev", "libxslt1-dev", "gcc", "libffi-dev", "libjpeg-dev", "libxml2-dev", "libyaml-dev", # "libxslt-dev", # This is a virtual pkg on Debian 8 "clang", # Not in specified sentry docs ]) def test__sentry_packages_prerequisites(Package, pkg): assert Package("{}".format(pkg)).is_installed def test__postgresql_is_installed(Package): pgsql = Package("postgresql-9.6") assert pgsql.is_installed assert pgsql.version >= "9.6"
  17. Testinfra: Service • is_running • is_enabled @pytest.mark.parametrize("srv", [ "redis-server", "postgresql",

    "nginx", ]) def test__req_services_are_running_and_enabled(Service, srv): assert Service(srv).is_running assert Service(srv).is_enabled
  18. Testinfra: Process • Obtiene procesos utilizando filter() o get(), los

    atributos están definidos en man ps(1) def test__processes(Process): processes = Process.filter(uname="sentry") mem = sum((p.pmem for p in processes)) assert mem < 40 # 40% of systems's memory
  19. Testinfra: modules de red • Interface, interfaces de red. •

    Socket, prueba sockets tcp/udp/unix. @pytest.mark.xfail(reason="IP could change") def test__interface(Interface): eth = Interface("eth0") assert eth.exists assert "10.0.2.15" in eth.addresses def test__postgres__is_listening(Socket): assert Socket("tcp://127.0.0.1:5432").is_listening def test__nginx__is_listening(Socket): assert Socket("tcp://0.0.0.0:80").is_listening
  20. Testinfra: modules de sistema. • SystemInfo, información del sistema. •

    Sysctl, parámetros del kernel. • MountPoint, puntos de montaje. def test__system_info(SystemInfo): assert SystemInfo.distribution == 'debian' assert SystemInfo.codename == 'jessie' assert SystemInfo.release == '8.6'
  21. Testinfra: otras herramientas • Supervisor, servicios bajo Supervisord • Salt,

    ejecuta módulos de Salt. • Ansible, ejecuta módulos de Ansible (tasks) • PuppetResource, obtiene recursos de Puppet. • Facter, obtiene facts con Facter
  22. TDI: Test Driven Infraestructure • Aplicar un ciclo TDD* a

    nuestra infraestructura • O más bien a la automatización de la misma. TESTS INFRAESTRUCTURA TESTS SISTEMA AUTOMATIZACIÓN
  23. Recursos Docs: • Pytest http://docs.pytest.org/en/latest/ • Requests http://docs.python-requests.org/en/latest/ • Testinfra

    http://testinfra.readthedocs.io/en/latest/ Blogs, vídeos, etc.: • The checklist manifesto, Atul Gawande. • Introduction to pytest https://www.youtube.com/watch?v=LdVJj65ikRY • Introduction to Unit Testing in Python with Pytest https://www.youtube.com/watch?v=UPanUFVFfzY • Five minutes with testinfra http://www.unixdaemon.net/tools/five-minutes-with-testinfra/ • System Testing with pytest and docker-py https://www.youtube.com/watch?v=EDpwvvU-sPY • Pytest desde las trincheras https://www.youtube.com/watch?v=q3549t2EalY • A beginner's guide to testing infrastructure as code https://www.ansible.com/beginners-guide-to-testing-infrastructure-as-code