Infrastructure Behavior Driven-Development
(WIP)
Ahí más inventores que inventos dice un amigo mio
.
Como Desarrollador y Operador de plataformas de software orientadas a telefonía (VOIP) y como practicante de TDD, me he visto envuelto en mayores responsabilidades y por lo tanto en la necesidad de mejorar el proceso de configuración y mantenimiento de los diferentes servicios, en este ejercicio he llegado a la conclusión que muchos otros ya han llegado y es usar la práctica de un entorno de pruebas automatizadas para configurar y probar los servicios.
Lo que buscamos de esta práctica es:
- que la configuración de servicios entre en un ciclo de integración y despliegue continuo.
- que la configuración de los servicios este orientado a comportamientos esperados.
- tener un mecanismo para obtener rápidamente feedback en la configuración de los servicios.
Para ilustrar como proceder vamos a configurar el servicio pure-ftpd en base a una serie de requerimientos, usando una librería de pruebas automatizadas, en este caso usare rspec y ruby para el ejercicio.
inicializamos el entorno de pruebas de rspec
rspec --init
iniciamos con una prueba fundamental y es verificar la sintaxis de configuración.
require 'spec_helper'
require 'tempfile'
def validate_syntax(config_path)
%x[timeout 1 /usr/sbin/pure-ftpd #{config_path}]
$?.exitstatus == 0 || $?.exitstatus == 124
end
describe 'pure-ftpd' do
let (:conf) { Tempfile.new('pure-ftpd') }
describe 'configuracion' do
it 'al verificar archivo invalido falla' do
conf.write('invalidline')
conf.flush
expect(validate_syntax(conf.path)).to be false
end
it 'al verificar archivo valido ok' do
conf.write('ChrootEveryone yes')
conf.flush
expect(validate_syntax(conf.path)).to be true
end
end
end
una vez tenemos un mecanismo para confirmar que la sintaxis del archivo es correcta procedemos a confirmar que el servicio inicializa y finaliza correctamente en presencia del archivo de configuracion indicado.
require 'spec_helper'
require 'tempfile'
def ftpd_start(config_path)
pid = Process.spawn("/usr/sbin/pure-ftpd #{config_path}")
Process.detach(pid)
sleep 1
port = %x{lsof -p #{pid} -itcp -a -P -n 2> /dev/null}.chomp[/TCP.+:(\d+)/,1].to_i
{pid: pid, port: port}
end
def ftpd_alive?(server)
# http://dev.housetrip.com/2014/03/24/ruby-pid-tip/
Process.kill(0, server[:pid])
true
rescue Errno::ESRCH
false
end
def ftpd_stop(server)
Process.kill(9, server[:pid])
rescue Errno::ESRCH
false
end
describe 'pure-ftpd' do
let (:conf) { Tempfile.new('pure-ftpd') }
describe 'gestión del servicio' do
it 'iniciar cuando el archivo de configuracion es correcto' do
conf.write('Bind 127.0.0.1,0')
conf.flush
pid = ftpd_start(conf.path)
expect(ftpd_alive?(pid)).to be true
ensure
ftpd_stop(pid)
end
it 'not iniciar cuando el archivo de configuracion es invalido' do
conf.write('asdfs')
conf.flush
pid = ftpd_start(conf.path)
expect(ftpd_alive?(pid)).to be false
ensure
ftpd_stop(pid)
end
end
end
los ejercicios anteriores nos empiezan a dar una idea de como vamos a controlar el servicio durante las pruebas, ahora vamos a proceder a configurar el servicio en base los requerimientos.
pure-ftpd.conf
Bind 127.0.0.1,8021
# funcion de utilidad para reescribir archivos de configuracion durante las pruebas
def substitute(path, match, replace)
content = File.read(path)
File.write(path, content.sub(match, replace))
end
before { substitute('pure-ftpd.conf', '/etc/pure-ftpd.pdb', "#{Dir.pwd}/pure-ftpd.pdb") }
no se permite ingreso anonimo
pure-ftpd.conf
NoAnonymous yes
it 'no se permite logeo anonimo' do
server = ftpd_start(conf)
expect do
Net::FTP.open("127.0.0.1", port: server[:port]) do |ftp|
ftp.login
end
end.to raise_error(Net::FTPPermError)
ensure
ftpd_stop(server)
end
ingreso solo a usuarios autorizados
pure-pw useradd foo -f pure-ftpd.users -u nobody -d /tmp/foo
pure-pw mkdb pure-ftpd.pdb -f pure-ftpd.users
pure-ftpd.conf
PureDB /etc/pure-ftpd.pdb
it 'ingreso a usuarios registrados' do
server = ftpd_start(conf)
expect do
Net::FTP.open("127.0.0.1", port: server[:port]) do |ftp|
ftp.login('foo', 'foo')
end
end.not_to raise_error
ensure
ftpd_stop(server)
end
it 'subir archivos solo usuarios registrados' do
server = ftpd_start(conf)
expect do
Net::FTP.open("127.0.0.1", port: server[:port]) do |ftp|
ftp.login('foo', 'foo')
ftp.puttextfile(__FILE__, 'demo.txt')
end
end.not_to raise_error
ensure
ftpd_stop(server)
end
Como vemos es posible usar las pruebas automatizadas como mecanismo para confirmar el comportamiento esperado del servicio, algunos beneficios que tenemos son:
- pruebas automatizadas de confirmación.
- verificar que el servicio siempre inicie (cuantas veces nos ha pasado que hemos modificado incorrectamente un archivo de configuración y luego no inicia?).
- nos incentiva a llevar los archivos de configuraciones en control de versiones.